Merge m-c to s-c.

This commit is contained in:
Richard Newman 2013-03-30 22:49:08 -07:00
commit c37ec8d740
706 changed files with 102106 additions and 47850 deletions

View File

@ -35,7 +35,7 @@ DIST_GARBAGE = config.cache config.log config.status* config-defs.h \
netwerk/necko-config.h xpcom/xpcom-config.h xpcom/xpcom-private.h \ netwerk/necko-config.h xpcom/xpcom-config.h xpcom/xpcom-private.h \
$(topsrcdir)/.mozconfig.mk $(topsrcdir)/.mozconfig.out $(topsrcdir)/.mozconfig.mk $(topsrcdir)/.mozconfig.out
default alldep all:: $(topsrcdir)/configure config.status default alldep all:: CLOBBER $(topsrcdir)/configure config.status
$(RM) -r $(DIST)/sdk $(RM) -r $(DIST)/sdk
$(RM) -r $(DIST)/include $(RM) -r $(DIST)/include
$(RM) -r $(DIST)/private $(RM) -r $(DIST)/private
@ -43,6 +43,12 @@ default alldep all:: $(topsrcdir)/configure config.status
$(RM) $(DIST)/bin/chrome.manifest $(DIST)/bin/components/components.manifest $(RM) $(DIST)/bin/chrome.manifest $(DIST)/bin/components/components.manifest
$(RM) -r _tests $(RM) -r _tests
CLOBBER: $(topsrcdir)/CLOBBER
@echo "STOP! The CLOBBER file has changed."
@echo "Please run the build through a sanctioned build wrapper, such as"
@echo "'mach build' or client.mk."
@exit 1
$(topsrcdir)/configure: $(topsrcdir)/configure.in $(topsrcdir)/configure: $(topsrcdir)/configure.in
@echo "STOP! configure.in has changed, and your configure is out of date." @echo "STOP! configure.in has changed, and your configure is out of date."
@echo "Please rerun autoconf and re-configure your build directory." @echo "Please rerun autoconf and re-configure your build directory."

View File

@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 sw=2 et tw=78: */
/* This Source Code Form is subject to the terms of the Mozilla Public /* 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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -1272,27 +1273,48 @@ HyperTextAccessible::NativeAttributes()
return attributes.forget(); return attributes.forget();
// For the html landmark elements we expose them like we do aria landmarks to // For the html landmark elements we expose them like we do aria landmarks to
// make AT navigation schemes "just work". Note html:header is redundant as // make AT navigation schemes "just work".
// a landmark since it usually contains headings. We're not yet sure how the nsIAtom* tag = mContent->Tag();
// web will use html:footer but our best bet right now is as contentinfo. if (tag == nsGkAtoms::nav) {
if (mContent->Tag() == nsGkAtoms::nav)
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("navigation")); NS_LITERAL_STRING("navigation"));
else if (mContent->Tag() == nsGkAtoms::section) } else if (tag == nsGkAtoms::section) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("region")); NS_LITERAL_STRING("region"));
else if (mContent->Tag() == nsGkAtoms::footer) } else if (tag == nsGkAtoms::header || tag == nsGkAtoms::footer) {
// Only map header and footer if they are not descendants
// of an article or section tag.
nsIContent* parent = mContent->GetParent();
while (parent) {
if (parent->Tag() == nsGkAtoms::article ||
parent->Tag() == nsGkAtoms::section)
break;
parent = parent->GetParent();
}
// No article or section elements found.
if (!parent) {
if (tag == nsGkAtoms::header) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("banner"));
} else if (tag == nsGkAtoms::footer) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("contentinfo"));
}
}
} else if (tag == nsGkAtoms::footer) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("contentinfo")); NS_LITERAL_STRING("contentinfo"));
else if (mContent->Tag() == nsGkAtoms::aside) } else if (tag == nsGkAtoms::aside) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("complementary")); NS_LITERAL_STRING("complementary"));
else if (mContent->Tag() == nsGkAtoms::article) } else if (tag == nsGkAtoms::article) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("article")); NS_LITERAL_STRING("article"));
else if (mContent->Tag() == nsGkAtoms::main) } else if (tag == nsGkAtoms::main) {
nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles, nsAccUtils::SetAccAttr(attributes, nsGkAtoms::xmlroles,
NS_LITERAL_STRING("main")); NS_LITERAL_STRING("main"));
}
return attributes.forget(); return attributes.forget();
} }

View File

@ -21,7 +21,12 @@
{ {
// Some AT may look for this // Some AT may look for this
testAttrs("nav", {"xml-roles" : "navigation"}, true); testAttrs("nav", {"xml-roles" : "navigation"}, true);
testAttrs("header", {"xml-roles" : "banner"}, true);
testAbsentAttrs("article_header", {"xml-roles" : "banner"});
testAbsentAttrs("section_header", {"xml-roles" : "banner"});
testAttrs("footer", {"xml-roles" : "contentinfo"}, true); testAttrs("footer", {"xml-roles" : "contentinfo"}, true);
testAbsentAttrs("article_footer", {"xml-roles" : "contentinfo"});
testAbsentAttrs("section_footer", {"xml-roles" : "contentinfo"});
testAttrs("aside", {"xml-roles" : "complementary"}, true); testAttrs("aside", {"xml-roles" : "complementary"}, true);
testAttrs("section", {"xml-roles" : "region"}, true); testAttrs("section", {"xml-roles" : "region"}, true);
testAttrs("main", {"xml-roles" : "main"}, true); // // ARIA override testAttrs("main", {"xml-roles" : "main"}, true); // // ARIA override
@ -68,13 +73,27 @@
title="HTML5 article element should expose xml-roles:article object attribute"> title="HTML5 article element should expose xml-roles:article object attribute">
Bug 761891 Bug 761891
</a> </a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=849624"
title="modify HTML5 header and footer accessibility API mapping">
Bug 849624
</a>
<p id="display"></p> <p id="display"></p>
<div id="content" style="display: none"></div> <div id="content" style="display: none"></div>
<pre id="test"> <pre id="test">
</pre> </pre>
<nav id="nav">a nav</nav> <nav id="nav">a nav</nav>
<header id="header">a header</header>
<footer id="footer">a footer</footer> <footer id="footer">a footer</footer>
<article id="article_with_header_and_footer">
<header id="article_header">a header within an article</header>
<footer id="article_footer">a footer within an article</footer>
</article>
<section id="section_with_header_and_footer">
<header id="section_header">a header within an section</header>
<footer id="section_footer">a footer within an section</footer>
</section>
<aside id="aside">by the way I am an aside</aside> <aside id="aside">by the way I am an aside</aside>
<section id="section">a section</section> <section id="section">a section</section>
<article id="main" role="main">a main area</article> <article id="main" role="main">a main area</article>

View File

@ -96,10 +96,8 @@
} }
if (MAC && (navigator.userAgent.indexOf("Mac OS X 10.6") != -1)) { if (MAC && (navigator.userAgent.indexOf("Mac OS X 10.6") != -1)) {
todo(false, todo(false,
"Re-enable on Mac OS 10.6 after fixing bug 845095 - intermittent orange"); "Re-enable on Mac OS 10.6 after fixing bug 845095 - intermittent orange");
SimpleTest.finish();
} else { } else {
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTest); addA11yLoadEvent(doTest);

View File

@ -20,7 +20,8 @@ const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1']. const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
getService(Ci.mozIJSSubScriptLoader); getService(Ci.mozIJSSubScriptLoader);
const prefService = Cc['@mozilla.org/preferences-service;1']. const prefService = Cc['@mozilla.org/preferences-service;1'].
getService(Ci.nsIPrefService); getService(Ci.nsIPrefService).
QueryInterface(Ci.nsIPrefBranch);
const appInfo = Cc["@mozilla.org/xre/app-info;1"]. const appInfo = Cc["@mozilla.org/xre/app-info;1"].
getService(Ci.nsIXULAppInfo); getService(Ci.nsIXULAppInfo);
const vc = Cc["@mozilla.org/xpcom/version-comparator;1"]. const vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
@ -127,12 +128,21 @@ function startup(data, reasonCode) {
if (name == 'addon-sdk') if (name == 'addon-sdk')
paths['tests/'] = prefixURI + name + '/tests/'; paths['tests/'] = prefixURI + name + '/tests/';
let useBundledSDK = options['force-use-bundled-sdk'];
if (!useBundledSDK) {
try {
useBundledSDK = prefService.getBoolPref("extensions.addon-sdk.useBundledSDK");
}
catch (e) {
// Pref doesn't exist, allow using Firefox shipped SDK
}
}
// Starting with Firefox 21.0a1, we start using modules shipped into firefox // Starting with Firefox 21.0a1, we start using modules shipped into firefox
// Still allow using modules from the xpi if the manifest tell us to do so. // Still allow using modules from the xpi if the manifest tell us to do so.
// And only try to look for sdk modules in xpi if the xpi actually ship them // And only try to look for sdk modules in xpi if the xpi actually ship them
if (options['is-sdk-bundled'] && if (options['is-sdk-bundled'] &&
(vc.compare(appInfo.version, '21.0a1') < 0 || (vc.compare(appInfo.version, '21.0a1') < 0 || useBundledSDK)) {
options['force-use-bundled-sdk'])) {
// Maps sdk module folders to their resource folder // Maps sdk module folders to their resource folder
paths[''] = prefixURI + 'addon-sdk/lib/'; paths[''] = prefixURI + 'addon-sdk/lib/';
// test.js is usually found in root commonjs or SDK_ROOT/lib/ folder, // test.js is usually found in root commonjs or SDK_ROOT/lib/ folder,

View File

@ -17,8 +17,8 @@
<em:targetApplication> <em:targetApplication>
<Description> <Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>18.0</em:minVersion> <em:minVersion>19.0</em:minVersion>
<em:maxVersion>21.0a1</em:maxVersion> <em:maxVersion>22.0a1</em:maxVersion>
</Description> </Description>
</em:targetApplication> </em:targetApplication>

View File

@ -135,7 +135,7 @@ We'd like to thank our many Jetpack project contributors! They include:
### P ### ### P ###
* Robert Pankowecki * Robert Pankowecki
* [Jamie Phelps](https://github.com/ongaeshi) * [Jamie Phelps](https://github.com/jxpx777)
* [Alexandre Poirot](https://github.com/ochameau) * [Alexandre Poirot](https://github.com/ochameau)
* Nickolay Ponomarev * Nickolay Ponomarev

View File

@ -59,50 +59,6 @@ argument to the constructor:
} }
}); });
## Timing Issues Using postMessage ##
Content scripts are loaded according to the value of the
[`contentScriptWhen`](dev-guide/guides/content-scripts/loading.html)
option: until that point is reached, any attempt to send a message to
the script using `postMessage()` will trigger an exception, probably
this:
<span class="aside">
This is a generic message which is emitted whenever we try to
send a message to a content script, but can't find the worker
which is supposed to receive it.
</span>
<pre>
Error: Couldn't find the worker to receive this message. The script may not be initialized yet, or may already have been unloaded.
</pre>
So code like this, where we create a panel and then
synchronously send it a message using `postMessage()`, will not work:
var data = require("sdk/self").data;
var panel = require("sdk/panel").Panel({
contentURL: "http://www.bbc.co.uk/mobile/index.html",
contentScriptFile: data.url("panel.js")
});
panel.postMessage("hi from main.js");
[`port.emit()`](dev-guide/guides/content-scripts/using-port.html)
queues messages until the content script is ready to receive them,
so the equivalent code using `port.emit()` will work:
var data = require("sdk/self").data;
var panel = require("sdk/panel").Panel({
contentURL: "http://www.bbc.co.uk/mobile/index.html",
contentScriptFile: data.url("panel.js")
});
panel.port.emit("hi from main.js");
## Message Events Versus User-Defined Events ## ## Message Events Versus User-Defined Events ##
You can use message events as an alternative to user-defined events: You can use message events as an alternative to user-defined events:

View File

@ -51,19 +51,15 @@ is specific to an event emitter and is included with its documentation.
whenever the event occurs. The arguments that will be passed to the listener whenever the event occurs. The arguments that will be passed to the listener
are specific to an event type and are documented with the event emitter. are specific to an event type and are documented with the event emitter.
For example, the following add-on registers two listeners with the For example, the following add-on registers a listener with the
[`private-browsing`](modules/sdk/private-browsing.html) module to [`tabs`](modules/sdk/tabs.html) module to
listen for the `start` and `stop` events, and logs a string to the console listen for the `ready` event, and logs a string to the console
reporting the change: reporting the event:
var pb = require("sdk/private-browsing"); var tabs = require("sdk/tabs");
pb.on("start", function() { tabs.on("ready", function () {
console.log("Private browsing is on"); console.log("tab loaded");
});
pb.on("stop", function() {
console.log("Private browsing is off");
}); });
It is not possible to enumerate the set of listeners for a given event. It is not possible to enumerate the set of listeners for a given event.
@ -73,9 +69,8 @@ the event.
### Adding Listeners in Constructors ### ### Adding Listeners in Constructors ###
Event emitters may be modules, as is the case for the Event emitters may be modules, as is the case for the `ready` event
`private-browsing` events, or they may be objects returned by above, or they may be objects returned by constructors.
constructors.
In the latter case the `options` object passed to the constructor typically In the latter case the `options` object passed to the constructor typically
defines properties whose names are the names of supported event types prefixed defines properties whose names are the names of supported event types prefixed
@ -126,28 +121,36 @@ supplying the type of event and the listener to remove.
The listener must have been previously been added using one of the methods The listener must have been previously been added using one of the methods
described above. described above.
In the following add-on, we add two listeners to private-browsing's `start` In the following add-on, we add two listeners to the
event, enter and exit private browsing, then remove the first listener and [`tabs` module's `ready` event](modules/sdk/tabs.html#ready).
enter private browsing again. One of the handler functions removes the listener again.
var pb = require("sdk/private-browsing"); Then we open two tabs.
var tabs = require("sdk/tabs");
function listener1() { function listener1() {
console.log("Listener 1"); console.log("Listener 1");
pb.removeListener("start", listener1); tabs.removeListener("ready", listener1);
} }
function listener2() { function listener2() {
console.log("Listener 2"); console.log("Listener 2");
} }
pb.on("start", listener1); tabs.on("ready", listener1);
pb.on("start", listener2); tabs.on("ready", listener2);
pb.activate(); tabs.open("https://www.mozilla.org");
pb.deactivate(); tabs.open("https://www.mozilla.org");
pb.activate();
Removing listeners is optional since they will be removed in any case We should see output like this:
when the application or add-on is unloaded.
<pre>
info: tabevents: Listener 1
info: tabevents: Listener 2
info: tabevents: Listener 2
</pre>
Listeners will be removed automatically when the add-on is unloaded.

View File

@ -2,124 +2,221 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this - 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/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
# Package Specification # # package.json #
A *package* is a directory that, at minimum, contains a JSON file The "package.json" file contains metadata for your add-on.
called `package.json`. This file is also referred to as the
*package manifest*.
## The Package Manifest ## Some of its entries, such as [`icon`](dev-guide/package-spec.html#icon),
[`name`](dev-guide/package-spec.html#name), and
[`description`](dev-guide/package-spec.html#description), have
direct analogues in the
[install manifest](https://developer.mozilla.org/en-US/docs/Install_Manifests)
format, and entries from package.json are written into the install
manifest when the add-on is built using [`cfx xpi`](dev-guide/cfx-tool.html#cfx xpi).
Others, such as
[`lib`](dev-guide/package-spec.html#lib),
[`permissions`](dev-guide/package-spec.html#permissions),
and [`preferences`](dev-guide/package-spec.html#preferences),
represent instructions to the cfx tool itself to generate and include
particular code and data structures in your add-on.
The `package.json` file is initially generated in your add-on's root
directory the first time you run
[`cfx init`](dev-guide/cfx-tool.html#cfx init). It looks like this
(assuming the add-on's directory is "my-addon"):
<pre>
{
"name": "my-addon",
"fullName": "my-addon",
"description": "a basic add-on",
"author": "",
"license": "MPL 2.0",
"version": "0.1"
}
</pre>
`package.json` may contain the following keys: `package.json` may contain the following keys:
* `name` - the name of the package. The package system will only load <table>
one package with a given name. This name cannot contain spaces or periods.
The name defaults to the name of the parent directory. If the package is
ever built as an XPI and the `fullName` key is not present, this is
used as the add-on's `em:name` element in its `install.rdf`.
* `fullName` - the full name of the package. It can contain spaces. If <colgroup>
the package is ever built as an XPI, this is used as the add-on's <col width="20%"></col>
`em:name` element in its `install.rdf`. <col width="80%"></col>
</colgroup>
* `description` - a String describing the package. If the package is <tr>
ever built as an XPI, this is used as the add-on's <td id="author"><code>author</code></td>
`em:description` element in its `install.rdf`. <td><p>The original author of the package. Defaults to an empty string.
It may include a optional URL in parentheses and an email
address in angle brackets.</p>
<p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#creator"><code>em:creator</code></a>
element in its "install.rdf".</p></td>
</tr>
* `author` - the original author of the package. The author may be a <tr>
String including an optional URL in parentheses and optional email <td id="contributors"><code>contributors</code></td>
address in angle brackets. If the package is ever built as an XPI, <td><p>An array of additional <a href="dev-guide/package-spec.html#author"><code>author</code></a>
this is used as the add-on's `em:creator` element in its strings.</p>
`install.rdf`. <p>These values will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#contributor"><code>em:contributor</code></a>
elements in its "install.rdf".</p></td>
</tr>
* `translators` - an Array of Strings consisted of translators of the package. <tr>
<td id="dependencies"><code>dependencies</code></td>
<td><p>String or array of strings representing package
names that this add-on requires in order to function properly.</p></td>
</tr>
* `contributors` - may be an Array of additional author Strings. <tr>
<td id="description"><code>description</code></td>
<td><p>The add-on's description. This defaults to the text
<code>"a basic add-on"</code>.</p>
<p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#description"><code>em:description</code></a>
element in its "install.rdf".</p></td>
</tr>
* `homepage` - the URL of the package's website. <tr>
<td id="fullName"><code>fullName</code></td>
<td><p>The full name of the package. It can contain spaces.<p></p>
If this key is present its value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#name"><code>em:name</code></a>
element in its "install.rdf".</p></td>
</tr>
* `icon` - the relative path from the root of the package to a <tr>
PNG file containing the icon for the package. By default, this <td id="harnessClassID"><code>harnessClassID</code></td>
is `icon.png`. If the package is built as an XPI, this is used <td><p>String in the <a href="https://developer.mozilla.org/en-US/docs/Generating_GUIDs">GUID format</a>.</p>
as the add-on's icon to display in the Add-on Manager's add-ons list. <p>This is used as a
This key maps on to the <a href="https://developer.mozilla.org/en-US/docs/Creating_XPCOM_Components/An_Overview_of_XPCOM#CID"><code>classID</code></a>
[`iconURL` entry in the Install Manifest](https://developer.mozilla.org/en/install_manifests#iconURL), of the "harness service" XPCOM component. Defaults to a random GUID generated by <code>cfx</code>.</p></td>
so the icon may be up to 48x48 pixels in size. </tr>
* `icon64` - the relative path from the root of the package to a <tr>
PNG file containing the icon64 for the package. By default, this <td id="homepage"><code>homepage</code></td>
is `icon64.png`. If the package is built as an XPI, this is used <td><p>The URL of the add-on's website.</p>
as the add-on's icon to display in the Addon Manager's add-on details view. <p>This value will be used as the add-on's
This key maps on to the <a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#homepageURL"><code>em:homepageURL</code></a>
[`icon64URL` entry in the Install Manifest](https://developer.mozilla.org/en/install_manifests#icon64URL), element in its "install.rdf".</p></td>
so the icon should be 64x64 pixels in size. </tr>
* `preferences` - *experimental* <tr>
An array of JSON objects that use the following keys `name`, `type`, `value`, <td id="icon"><code>icon</code></td>
`title`, and `description`. These JSON objects will be used to automatically <td><p>The relative path from the root of the add-on to a
create a preferences interface for the addon in the Add-ons Manager. PNG file containing the icon for the add-on. Defaults to
For more information see the documentation of [simple-prefs](modules/sdk/simple-prefs.html). <code>"icon.png"</code>.</p>
<p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#iconURL"><code>em:iconURL</code></a>
element in its "install.rdf".</p>
<p>The icon may be up to 48x48 pixels in size.</p></td>
</tr>
* `license` - the name of the license as a String, with an optional <tr>
URL in parentheses. <td id="icon64"><code>icon64</code></td>
<td><p>The relative path from the root of the add-on to a
PNG file containing the large icon for the add-on. Defaults to
<code>"icon64.png"</code>.</p>
<p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#icon64URL"><code>em:icon64URL</code></a>
element in its "install.rdf".</p>
<p>The icon may be up to 64x64 pixels in size.</p></td>
</tr>
* `id` - a globally unique identifier for the package. When the package is <tr>
built as an XPI, this is used as the add-on's `em:id` element in its <td id="id"><code>id</code></td>
`install.rdf`. See the <td><p>A globally unique identifier for the add-on.</p>
[Program ID page](dev-guide/guides/program-id.html). <p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#id"><code>em:id</code></a>
element in its "install.rdf".</p>
<p>See the <a href="dev-guide/guides/program-id.html">Program ID documentation</a>.</p></td>
</tr>
* `version` - a String representing the version of the package. If the <tr>
package is ever built as an XPI, this is used as the add-on's <td id="lib"><code>lib</code></td>
`em:version` element in its `install.rdf`. <td><p>String representing the top-level module directory provided in
this add-on. Defaults to <code>"lib"</code>.</p></td>
</tr>
* `dependencies` - a String or Array of Strings representing package <tr>
names that this package requires in order to function properly. <td id="license"><code>license</code></td>
<td><p>The name of the license under which the add-on is distributed, with an optional
URL in parentheses. Defaults to <code>"MPL 2.0"</code>.</p></td>
</tr>
* `lib` - a String representing the top-level module directory provided in <tr>
this package. Defaults to `"lib"`. <td id="main"><code>main</code></td>
<td><p>String representing the name of a program module that is
* `tests` - a String representing the top-level module directory containing
test suites for this package. Defaults to `"tests"`.
* `packages` - a String or Array of Strings representing paths to
directories containing additional packages, defaults to
`"packages"`.
* `main` - a String representing the name of a program module that is
located in one of the top-level module directories specified by located in one of the top-level module directories specified by
`lib`. Defaults to `"main"`. <a href="dev-guide/package-spec.html#lib"><code>lib</code></a>.
Defaults to <code>"main"</code>.</p></td>
</tr>
* `harnessClassID` - a String in the GUID format: <tr>
`xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, where `x` represents a single <td id="name"><code>name</code></td>
hexadecimal digit. It is used as a `classID` (CID) of the "harness service" <td><p>The add-on's name. This name cannot contain spaces or periods, and
XPCOM component. Defaults to a random GUID generated by `cfx`. defaults to the name of the parent directory.</p><p>When the add-on is
built as an XPI, if the <a href="dev-guide/package-spec.html#fullName"><code>fullName</code></a>
key is not present, <code>name</code> is used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#name"><code>em:name</code></a>
element in its "install.rdf".</p></td>
</tr>
* `permissions` - a set of permissions that the add-on needs. <tr>
* `private-browsing` - A Boolean indicating whether or not the <td id="packages"><code>packages</code></td>
package supports private browsing. If this value is not `true` <td><p>String or array of strings representing paths to
then the package will not see private windows. directories containing additional packages. Defaults to
<code>"packages"</code>.</p></td>
</tr>
## Documentation ## <tr>
<td id="permissions"><code>permissions</code></td>
<td><p>A set of permissions that the add-on needs.</p>
<p><strong><code>private-browsing</code></strong>: a boolean
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>
</td>
</tr>
A package may optionally contain a <tr>
[Markdown](http://daringfireball.net/projects/markdown/)-formatted file <td id="preferences"><code>preferences</code></td>
called `README.md` in its root directory. Package-browsing tools may display <td><p>An array of JSON objects that use the following keys:
this file to developers. <code>name</code>,<code>type</code>, <code>value</code>,
<code>title</code>, and <code>description</code>. These JSON objects will be used to
create a preferences interface for the add-on in the Add-ons Manager.</p>
<p>See the documentation for the
<a href="modules/sdk/simple-prefs.html"><code>simple-prefs</code> module</a>.</p></td>
</tr>
Additionally, Markdown files can be placed in an optional `docs` <tr>
directory. When package-browsing tools are asked to show the <td id="tests"><code>tests</code></td>
documentation for a module, they will look in this directory for a <td><p>String representing the top-level module directory containing
`.md` file with the module's name. Thus, for instance, if a user test suites for this package. Defaults to <code>"tests"</code>.</p></td>
browses to a module at `lib/foo/bar.js`, the package-browsing tool </tr>
will look for a file at `docs/foo/bar.js` to represent the module's
API documentation.
## Data Resources ## <tr>
<td id="translators"><code>translators</code></td>
<td><p>An array of strings listing translators of this add-on.</p>
<p>These values will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#translator"><code>em:translator</code></a>
elements in its "install.rdf".</p></td>
</tr>
Packages may optionally contain a directory called `data` into which <tr>
arbitrary files may be placed, such as images or text files. The <td id="version"><code>version</code></td>
URL for these resources may be reached using the <td><p>String representing the version of the add-on. Defaults to
[self](modules/sdk/self.html) module. <code>"0.1"</code>.</p>
<p>This value will be used as the add-on's
<a href="https://developer.mozilla.org/en-US/docs/Install_Manifests#version"><code>em:version</code></a>
element in its "install.rdf".</p></td>
</tr>
</table>
[Markdown]: http://daringfireball.net/projects/markdown/
[non-bootstrapped XUL extension]: #guide/xul-extensions

View File

@ -50,8 +50,12 @@ the matcher tells the main add-on code, which displays the annotation panel.
We'll use the `simple-storage` module to store annotations. We'll use the `simple-storage` module to store annotations.
Because we are recording potentially sensitive information, we want to prevent Because we are recording potentially sensitive information, we want to prevent
the user creating annotations when in private browsing mode, so we'll use the the user creating annotations when in private browsing mode. The simplest way
`private-browsing` module for that. to do this is to omit the
[`"private-browsing"` key](dev-guide/package-spec.html#permissions) from the
add-on's "package.json" file. If we do this, then the add-on won't see any
private windows, and the annotator's widget will not appear in any private
windows.
## Getting Started ## ## Getting Started ##

View File

@ -277,93 +277,20 @@ under quota.)
## Respecting Private Browsing ## ## Respecting Private Browsing ##
Since annotations record the user's browsing history we should prevent the user Since annotations record the user's browsing history we should avoid recording
from creating annotations while the browser is in annotations in private windows.
[Private Browsing](http://support.mozilla.com/en-US/kb/Private%20Browsing) mode.
First let's import the `private-browsing` module into `main.js`: There's a very simple way to do this: do nothing. By omitting the
[`"private-browsing"` key](dev-guide/package-spec.html#permissions) from the
annotator's "package.json" file, the annotator opts out of private browsing
altogether.
var privateBrowsing = require('sdk/private-browsing'); This means that its widget will not appear on any private windows and its
selector and matcher content scripts won't run, so the user won't be able to
enter any annotations in private windows.
We already have a variable `annotatorIsOn` that we use to indicate whether the Try it: execute cfx run and open a new private window: you should no longer
user can enter annotations. But we don't want to use that here, because we want see the annotator's widget.
to remember the underlying state so that when they exit Private Browsing the
annotator is back in whichever state it was in before.
So we'll implement a function defining that to enter annotations, the annotator
must be active *and* Private Browsing must be off:
function canEnterAnnotations() {
return (annotatorIsOn && !privateBrowsing.isActive);
}
Next, everywhere we previously used `annotatorIsOn` directly, we'll call this
function instead:
function activateSelectors() {
selectors.forEach(
function (selector) {
selector.postMessage(canEnterAnnotations());
});
}
<br>
function toggleActivation() {
annotatorIsOn = !annotatorIsOn;
activateSelectors();
return canEnterAnnotations();
}
<br>
var selector = pageMod.PageMod({
include: ['*'],
contentScriptWhen: 'ready',
contentScriptFile: [data.url('jquery-1.4.2.min.js'),
data.url('selector.js')],
onAttach: function(worker) {
worker.postMessage(canEnterAnnotations());
selectors.push(worker);
worker.port.on('show', function(data) {
annotationEditor.annotationAnchor = data;
annotationEditor.show();
});
worker.on('detach', function () {
detachWorker(this, selectors);
});
}
});
We want to stop the user changing the underlying activation state when in
Private Browsing mode, so we'll edit `toggleActivation` again:
function toggleActivation() {
if (privateBrowsing.isActive) {
return false;
}
annotatorIsOn = !annotatorIsOn;
activateSelectors();
return canEnterAnnotations();
}
Finally, inside the `main` function, we'll add the following code to handle
changes in Private Browsing state by changing the icon and notifying the
selectors:
privateBrowsing.on('start', function() {
widget.contentURL = data.url('widget/pencil-off.png');
activateSelectors();
});
privateBrowsing.on('stop', function() {
if (canEnterAnnotations()) {
widget.contentURL = data.url('widget/pencil-on.png');
activateSelectors();
}
});
Try it: execute `cfx run`, and experiment with switching the annotator on and
off while in and out of Private Browsing mode.
Now we can create and store annotations, the last piece is to Now we can create and store annotations, the last piece is to
[display them when the user loads the [display them when the user loads the page](dev-guide/tutorials/annotator/displaying.html).
page](dev-guide/tutorials/annotator/displaying.html).

View File

@ -10,7 +10,7 @@ incompatible changes to them in future releases.</span>
The [guide to event-driven programming with the SDK](dev-guide/guides/events.html) The [guide to event-driven programming with the SDK](dev-guide/guides/events.html)
describes how to consume events: that is, how to listen to events generated describes how to consume events: that is, how to listen to events generated
by event targets. For example, you can listen to [`private-browsing`'s `start` event](modules/sdk/private-browsing.html#start) or the by event targets. For example, you can listen to the [`tabs` module's `ready` event](modules/sdk/tabs.html#ready) or the
[`Panel` object's `show` event](modules/sdk/panel.html#show). [`Panel` object's `show` event](modules/sdk/panel.html#show).
With the SDK, it's also simple to implement your own event targets. With the SDK, it's also simple to implement your own event targets.

View File

@ -5,12 +5,6 @@
<!-- contributed by Drew Willcoxon [adw@mozilla.com] --> <!-- contributed by Drew Willcoxon [adw@mozilla.com] -->
<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] --> <!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
The `context-menu` module lets you add items to Firefox's page context menu.
Introduction
------------
The `context-menu` API provides a simple, declarative way to add items to the The `context-menu` API provides a simple, declarative way to add items to the
page's context menu. You can add items that perform an action when clicked, page's context menu. You can add items that perform an action when clicked,
submenus, and menu separators. submenus, and menu separators.
@ -396,6 +390,15 @@ it's too long, and includes it in the returned string. When the item is shown,
its label will be "Search Google for `text`", where `text` is the truncated its label will be "Search Google for `text`", where `text` is the truncated
selection. selection.
## Private Windows ##
If your add-on has not opted into private browsing, then any menus or
menu items that you add will not appear in context menus belonging to
private browser windows.
To learn more about private windows, how to opt into private browsing, and how
to support private browsing, refer to the
[documentation for the `private-browsing` module](modules/sdk/private-browsing.html).
More Examples More Examples
------------- -------------

View File

@ -259,6 +259,16 @@ The following add-on creates a widget which, when clicked, highlights all the
} }
}); });
## Private Windows ##
If your add-on has not opted into private browsing, then your page-mods will
not attach content scripts to documents loaded into private windows, even if
their URLs match the pattern you have specified.
To learn more about private windows, how to opt into private browsing, and how
to support private browsing, refer to the
[documentation for the `private-browsing` module](modules/sdk/private-browsing.html).
<api name="PageMod"> <api name="PageMod">
@class @class
A page-mod object. Once created a page-mod will execute the supplied content A page-mod object. Once created a page-mod will execute the supplied content

View File

@ -31,6 +31,15 @@ in preparation for the next time it is shown.
Your add-on can receive notifications when a panel is shown or hidden by Your add-on can receive notifications when a panel is shown or hidden by
listening to its `show` and `hide` events. 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 ## ## Panel Content ##
The panel's content is specified as HTML, which is loaded from the URL The panel's content is specified as HTML, which is loaded from the URL
@ -376,6 +385,18 @@ when applying your own styles. For example, if you set the panel's
`background-color` property to `white` and do not set the `color` property, `background-color` property to `white` and do not set the `color` property,
then the panel's text will be invisible on OS X although it looks fine on Ubuntu. then the panel's text will be invisible on OS X although it looks fine on Ubuntu.
## Private Browsing ##
If your add-on has
[opted into private browsing](modules/sdk/private-browsing.html#Opting into private browsing),
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.
<api name="Panel"> <api name="Panel">
@class @class
The Panel object represents a floating modal dialog that can by an add-on to The Panel object represents a floating modal dialog that can by an add-on to
@ -400,6 +421,10 @@ Creates a panel.
The width of the panel in pixels. Optional. The width of the panel in pixels. Optional.
@prop [height] {number} @prop [height] {number}
The height of the panel in pixels. Optional. The height of the panel in pixels. Optional.
@prop [focus] {boolean}
Set to `false` to prevent taking the focus away when the panel is shown.
Only turn this off if necessary, to prevent accessibility issue.
Optional, default to `true`.
@prop [contentURL] {string} @prop [contentURL] {string}
The URL of the content to load in the panel. The URL of the content to load in the panel.
@prop [allow] {object} @prop [allow] {object}
@ -472,6 +497,12 @@ The height of the panel in pixels.
The width of the panel in pixels. The width of the panel in pixels.
</api> </api>
<api name="focus">
@property {boolean}
Whether of not focus will be taken away when the panel is shown.
This property is read-only.
</api>
<api name="contentURL"> <api name="contentURL">
@property {string} @property {string}
The URL of content loaded into the panel. This can point to The URL of content loaded into the panel. This can point to

View File

@ -6,90 +6,170 @@
<!-- edited by Noelle Murata [fiveinchpixie@gmail.com] --> <!-- edited by Noelle Murata [fiveinchpixie@gmail.com] -->
<!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] --> <!-- contributed by Irakli Gozalishvili [gozala@mozilla.com] -->
The `private-browsing` module allows you to access Firefox's private browsing ## Per-window private browsing ##
mode, detecting if it is active and when its state changes.
This module is available in all applications. However, only Firefox will ever Firefox 20 introduces per-window private browsing. This means that private
transition into or out of private browsing mode. For all other applications, browsing status is a property of an individual browser window.
`pb.isActive` will always be `false`, and none of the events will be emitted.
<div class="warning"> The user enters
The <a href="modules/sdk/private-browsing.html#activate()"><code>activate</code></a> private browsing by opening a new private browser window. When they do this,
and <a href="modules/sdk/private-browsing.html#deactivate()"><code>deactivate</code></a> any existing non-private windows are kept open, so the user will typically
functions, <a href="modules/sdk/private-browsing.html#isActive"><code>isActive</code></a> have both private and non-private windows open at the same time.
property, <a href="modules/sdk/private-browsing.html#start"><code>start</code></a>,
and <a href="modules/sdk/private-browsing.html#stop"><code>stop</code></a>
events are all
now deprecated due to per-window private browsing. They will continue to work
until version 1.13 of the SDK. From version 1.13 onwards they will still exist
but will have no effect when called.
</div>
<api name="isActive"> ## Opting into private browsing ##
@property {boolean}
This read-only boolean is `true` if global private browsing mode is turned on.
<div class="warning"> Add-ons built using the SDK must opt into private browsing by setting the
This property is deprecated. It will continue to work until version 1.13 of the SDK. following key in their [`package.json`](dev-guide/package-spec.html) file:
From version 1.13 onwards it will always return `false`.
</div> <pre>"permissions": {"private-browsing": true}</pre>
</api>
If an add-on has not opted in, then the high-level SDK modules will not
expose private windows, or objects (such as tabs) that are associated
with private windows:
* the [`windows`](modules/sdk/windows.html) module will not list any
private browser windows, generate any events for private browser windows,
or let the add-on open any private browser windows
* the [`tabs`](modules/sdk/tabs.html) module will not list any tabs that
belong to private browser windows, and the add-on won't receive any events
for such tabs
* any [`widgets`](modules/sdk/widget.html) will not be displayed in
private browser windows
* any menus or menu items created using the
[`context-menu`](modules/sdk/context-menu.html) will not be shown in
context menus that belong to private browser windows
* the [`page-mod`](modules/sdk/page-mod.html) module will not attach
content scripts to documents belonging to private browser windows
* any [`panel`](modules/sdk/panel.html) objects will not be shown if the
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
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
into private browsing.
## Respecting private browsing ##
The `private-browsing` module exports a single function
[`isPrivate()`](modules/sdk/private-browsing.html#isPrivate(object))
that takes an object, which may be a
[`BrowserWindow`](modules/sdk/windows.html#BrowserWindow),
[`tab`](modules/sdk/tabs.html#Tab), or
[`worker`](modules/sdk/content/worker.html#Worker),
as an argument. It returns `true` only if the object is:
* a private window, or
* a tab belonging to a private window, or
* a worker that's associated with a document hosted in a private window
Add-ons can use this API to decide whether or not to store user data.
For example, here's an add-on that stores the titles of tabs the user loads,
and uses `isPrivate()` to exclude the titles of tabs that were loaded into
private windows:
var simpleStorage = require("simple-storage");
if (!simpleStorage.storage.titles)
simpleStorage.storage.titles = [];
require("tabs").on("ready", function(tab) {
if (!require("private-browsing").isPrivate(tab)) {
console.log("storing...");
simpleStorage.storage.titles.push(tab.title);
}
else {
console.log("not storing, private data");
}
});
Here's an add-on that uses a [page-mod](modules/sdk/page-mod.html) to log
the content of pages loaded by the user, unless the page is private. In
the handler for the page-mod's [`attach`](modules/sdk/page-mod.html#attach)
event, it passes the worker into `isPrivate()`:
var pageMod = require("sdk/page-mod");
var privateBrowsing = require("sdk/private-browsing");
var loggingScript = "self.port.on('log-content', function() {" +
" console.log(document.body.innerHTML);" +
"});";
function logPublicPageContent(worker) {
if (privateBrowsing.isPrivate(worker)) {
console.log("private window, doing nothing");
}
else {
worker.port.emit("log-content");
}
}
pageMod.PageMod({
include: "*",
contentScript: loggingScript,
onAttach: logPublicPageContent
});
## Tracking private-browsing exit ##
Sometimes it can be useful to cache some data from private windows while they
are open, as long as you don't store it after the private browsing windows
have been closed. For example, the "Downloads" window might want to display
all downloads while there are still some private windows open, then clean out
all the private data when all private windows have closed.
To do this with the SDK, you can listen to the system event named
"last-pb-context-exited":
var events = require("sdk/system/events");
function listener(event) {
console.log("last private window closed");
}
events.on("last-pb-context-exited", listener);
## Working with Firefox 19 ##
In Firefox 19, private browsing is a global property for the entire browser.
When the user enters private browsing, the existing browsing session is
suspended and a new blank window opens. This window is private, as are any
other windows opened until the user chooses to exit private browsing, at which
point all private windows are closed and the user is returned to the original
non-private session.
When running on Firefox 19, `isPrivate()` will return true if and only if
the user has global private browsing enabled.
<api name="isPrivate"> <api name="isPrivate">
@function @function
Returns `true` if the argument is a private window or tab. Function to check whether the given object is private. It takes an
@param [thing] {any} object as an argument, and returns `true` only if the object is:
The thing to check if it is private, only handles windows and tabs at the moment.
Everything else returns `false` automatically. * a private [`BrowserWindow`](modules/sdk/windows.html#BrowserWindow) or
In global private browsing mode, this method returns the same value as `isActive`. * a [`tab`](modules/sdk/tabs.html#Tab) belonging to a private window, or
</api> * a [`worker`](modules/sdk/content/worker.html#Worker) that's associated
with a document hosted in a private window
<api name="activate">
@function @param [object] {any}
Turns on global private browsing mode. The object to check. This may be a
[`BrowserWindow`](modules/sdk/windows.html#BrowserWindow),
<div class="warning"> [`tab`](modules/sdk/tabs.html#Tab), or
This function is deprecated. It will continue to work until version 1.13 of the SDK. [`worker`](modules/sdk/content/worker.html#Worker).
From version 1.13 onwards it will still exist but will have no effect when called.
</div>
</api>
<api name="deactivate">
@function
Turns off global private browsing mode.
<div class="warning">
This function is deprecated. It will continue to work until version 1.13 of the SDK.
From version 1.13 onwards it will still exist but will have no effect when called.
</div>
</api>
<api name="start">
@event
Emitted immediately after global private browsing begins.
var pb = require("sdk/private-browsing");
pb.on("start", function() {
// Do something when the browser starts private browsing mode.
});
<div class="warning">
This event is deprecated. It will continue to work until version 1.13 of the SDK.
From version 1.13 onwards this event will not be emitted.
</div>
</api>
<api name="stop">
@event
Emitted immediately after global private browsing ends.
var pb = require("sdk/private-browsing");
pb.on("stop", function() {
// Do something when the browser stops private browsing mode.
});
<div class="warning">
This event is deprecated. It will continue to work until version 1.13 of the SDK.
From version 1.13 onwards this event will not be emitted.
</div>
</api> </api>

View File

@ -30,6 +30,14 @@ Discontiguous selections can be accessed by iterating over the `selection`
module itself. Each iteration yields a `Selection` object from which `text`, module itself. Each iteration yields a `Selection` object from which `text`,
`html`, and `isContiguous` properties can be accessed. `html`, and `isContiguous` properties can be accessed.
## Private Windows ##
If your add-on has not opted into private browsing, then you won't see any
selections made in tabs that are hosted by private browser windows.
To learn more about private windows, how to opt into private browsing, and how
to support private browsing, refer to the
[documentation for the `private-browsing` module](modules/sdk/private-browsing.html).
Examples Examples
-------- --------

View File

@ -53,8 +53,9 @@ downgrade
<api name="isPrivateBrowsingSupported"> <api name="isPrivateBrowsingSupported">
@property {boolean} @property {boolean}
This property indicates add-on's support for private browsing. It comes from the This property indicates whether or not the add-on supports private browsing.
`private-browsing` property set in the `package.json` file in the main package. It comes from the [`private-browsing` key](dev-guide/package-spec.html#permissions)
in the add-on's `package.json` file.
</api> </api>
<api name="data"> <api name="data">

View File

@ -172,33 +172,13 @@ data you remove is up to you. For example:
Private Browsing Private Browsing
---------------- ----------------
If your storage is related to your users' Web history, personal information, or If your storage is related to your users' Web history, personal information, or
other sensitive data, your add-on should respect [private browsing mode][SUMO]. other sensitive data, your add-on should respect
While private browsing mode is active, you should not store any sensitive data. [private browsing](http://support.mozilla.com/en-US/kb/Private+Browsing).
Because any kind of data can be placed into simple storage, support for private
browsing is not built into the module. Instead, use the
[`private-browsing`](modules/sdk/private-browsing.html) module to
check private browsing status and respond accordingly.
For example, the URLs your users visit should not be stored during private
browsing. If your add-on records the URL of the selected tab, here's how you
might handle that:
ss.storage.history = [];
var privateBrowsing = require("sdk/private-browsing");
if (!privateBrowsing.isActive) {
var url = getSelectedTabURL();
ss.storage.history.push(url);
}
For more information on supporting private browsing, see its [Mozilla Developer
Network documentation][MDN]. While that page does not apply specifically to
SDK-based add-ons (and its code samples don't apply at all), you should follow
its guidance on best practices and policies.
[SUMO]: http://support.mozilla.com/en-US/kb/Private+Browsing
[MDN]: https://developer.mozilla.org/En/Supporting_private_browsing_mode
To read about how to opt into private browsing mode and how to use the
SDK to avoid storing user data associated with private windows, refer to the
documentation for the
[`private-browsing` module](modules/sdk/private-browsing.html).
<api name="storage"> <api name="storage">
@property {object} @property {object}

View File

@ -13,10 +13,12 @@ You can find a list of events dispatched by firefox codebase
var events = require("sdk/system/events"); var events = require("sdk/system/events");
var { Ci } = require("chrome"); var { Ci } = require("chrome");
events.on("http-on-modify-request", function (event) { function listener(event) {
var channel = event.subject.QueryInterface(Ci.nsIHttpChannel); var channel = event.subject.QueryInterface(Ci.nsIHttpChannel);
channel.setRequestHeader("User-Agent", "MyBrowser/1.0", false); channel.setRequestHeader("User-Agent", "MyBrowser/1.0", false);
}); }
events.on("http-on-modify-request", listener);
<api name="emit"> <api name="emit">
@function @function

View File

@ -68,6 +68,18 @@ content:
}); });
}); });
## Private Windows ##
If your add-on has not opted into private browsing, then you won't see any
tabs that are hosted by private browser windows.
Tabs hosted by private browser windows won't be seen if you enumerate the
`tabs` module itself, and you won't receive any events for them.
To learn more about private windows, how to opt into private browsing, and how
to support private browsing, refer to the
[documentation for the `private-browsing` module](modules/sdk/private-browsing.html).
<api name="activeTab"> <api name="activeTab">
@property {Tab} @property {Tab}
@ -140,8 +152,8 @@ If present and true, the new tab will be opened to the right of the active tab
and will not be active. This is an optional property. and will not be active. This is an optional property.
@prop isPrivate {boolean} @prop isPrivate {boolean}
Boolean which will determine if a private tab should be opened. Boolean which will determine whether the new tab should be private or not.
Private browsing mode must be supported in order to do this. If your add-on does not support private browsing this will have no effect.
See the [private-browsing](modules/sdk/private-browsing.html) documentation for more information. See the [private-browsing](modules/sdk/private-browsing.html) documentation for more information.
@prop [isPinned] {boolean} @prop [isPinned] {boolean}

View File

@ -39,8 +39,17 @@ A browser window.
<api name="getTabs"> <api name="getTabs">
@function @function
Returns the tabs for the specified `window`, or the tabs Returns the tabs for the specified `window`.
across all the browser's windows if `window` is omitted.
If you omit `window`, this function will return tabs
across all the browser's windows. However, if your add-on
has not opted into private browsing, then the function will
exclude all tabs that are hosted by private browser windows.
To learn more about private windows, how to opt into private browsing, and how
to support private browsing, refer to the
[documentation for the `private-browsing` module](modules/sdk/private-browsing.html).
@param window {nsIWindow} @param window {nsIWindow}
Optional. Optional.
@returns {Array} @returns {Array}

View File

@ -346,6 +346,15 @@ listener, the panel will not be anchored:
See [bug 638142](https://bugzilla.mozilla.org/show_bug.cgi?id=638142). See [bug 638142](https://bugzilla.mozilla.org/show_bug.cgi?id=638142).
## Private Windows ##
If your add-on has not opted into private browsing, then your widget will
not appear in any private browser windows.
To learn more about private windows, how to opt into private browsing, and how
to support private browsing, refer to the
[documentation for the `private-browsing` module](modules/sdk/private-browsing.html).
## Examples ## ## Examples ##
For conciseness, these examples create their content scripts as strings and use For conciseness, these examples create their content scripts as strings and use
@ -685,6 +694,9 @@ The related `WidgetView` object.
<api name="click"> <api name="click">
@event @event
This event is emitted when the widget is clicked. This event is emitted when the widget is clicked.
@argument {WidgetView}
Listeners are passed a single argument which is the `WidgetView` that triggered the click event.
</api> </api>
<api name="message"> <api name="message">

View File

@ -5,6 +5,21 @@
The `window/utils` module provides helper functions for working with The `window/utils` module provides helper functions for working with
application windows. application windows.
## Private Windows ##
With this module your add-on will see private browser windows
even if it has not explicitly opted into private browsing, so you need
to take care not to store any user data derived from private browser windows.
The exception is the [`windows()`](modules/sdk/window/utils.html#windows())
function which returns an array of currently opened windows. Private windows
will not be included in this list if your add-on has not opted into private
browsing.
To learn more about private windows, how to opt into private browsing, and how
to support private browsing, refer to the
[documentation for the `private-browsing` module](modules/sdk/private-browsing.html).
<api name="getMostRecentBrowserWindow"> <api name="getMostRecentBrowserWindow">
@function @function
Get the topmost browser window, as an Get the topmost browser window, as an
@ -169,6 +184,11 @@ element.
@function @function
Returns an array of all currently opened windows. Returns an array of all currently opened windows.
Note that these windows may still be loading. Note that these windows may still be loading.
If your add-on has not
[opted into private browsing](modules/sdk/private-browsing.html),
any private browser windows will not be included in the array.
@returns {Array} @returns {Array}
Array of `nsIDOMWindow` instances. Array of `nsIDOMWindow` instances.
</api> </api>

View File

@ -4,21 +4,29 @@
<!-- contributed by Felipe Gomes [felipc@gmail.com] --> <!-- contributed by Felipe Gomes [felipc@gmail.com] -->
The `windows` module provides basic functions for working with browser
windows. With this module, you can:
The `windows` module provides easy access to browser windows, their * [enumerate the currently opened browser windows](modules/sdk/windows.html#browserWindows)
tabs, and open/close related functions and events. * [open new browser windows](modules/sdk/windows.html#open(options))
* [listen for common window events such as open and close](modules/sdk/windows.html#Events)
This module currently only supports browser windows and does not provide ## Private Windows ##
access to non-browser windows such as the Bookmarks Library, preferences
or other non-browser windows created via add-ons. If your add-on has not opted into private browsing, then you won't see any
private browser windows. Private browser windows won't appear in the
[`browserWindows`](modules/sdk/windows.html#browserWindows) property, you
won't receive any window events, and you won't be able to open private
windows.
To learn more about private windows, how to opt into private browsing, and how
to support private browsing, refer to the
[documentation for the `private-browsing` module](modules/sdk/private-browsing.html).
<api name="browserWindows"> <api name="browserWindows">
@property {List} @property {List}
An object that contains various properties and methods to access `browserWindows` provides access to all the currently open browser windows as
functionality from browser windows, such as opening new windows, accessing [BrowserWindow](modules/sdk/windows.html#BrowserWindow) objects.
their tabs or switching the current active window.
`browserWindows` provides access to all the currently open browser windows:
var windows = require("sdk/windows"); var windows = require("sdk/windows");
for each (var window in windows.browserWindows) { for each (var window in windows.browserWindows) {
@ -27,9 +35,7 @@ their tabs or switching the current active window.
console.log(windows.browserWindows.length); console.log(windows.browserWindows.length);
Object emits all the events listed under "Events" section. This object emits all the events listed under the "Events" section:
####Examples####
var windows = require("sdk/windows").browserWindows; var windows = require("sdk/windows").browserWindows;
@ -119,8 +125,8 @@ String URL to be opened in the new window.
This is a required property. This is a required property.
@prop isPrivate {boolean} @prop isPrivate {boolean}
Boolean which will determine if a private window should be opened. Boolean which will determine whether the new window should be private or not.
Private browsing mode must be supported in order to do this. If your add-on does not support private browsing this will have no effect.
See the [private-browsing](modules/sdk/private-browsing.html) documentation for more information. See the [private-browsing](modules/sdk/private-browsing.html) documentation for more information.
@prop [onOpen] {function} @prop [onOpen] {function}
@ -185,8 +191,7 @@ Returns `true` if the window is in private browsing mode, and `false` otherwise.
<div class="warning"> <div class="warning">
This property is deprecated. This property is deprecated.
From version 1.14, please consider using following code instead:<br/> From version 1.14, use the <a href="modules/sdk/private-browsing.html#isPrivate()">private-browsing module's <code>isPrivate()</code></a> function instead.
<code>require("private-browsing").isPrivate(browserWindow)</code>
</div> </div>
</api> </api>

View File

@ -8,7 +8,6 @@ var data = require('self').data;
var panels = require('panel'); var panels = require('panel');
var simpleStorage = require('simple-storage'); var simpleStorage = require('simple-storage');
var notifications = require("notifications"); var notifications = require("notifications");
var privateBrowsing = require('private-browsing');
/* /*
Global variables Global variables
@ -32,13 +31,6 @@ function updateMatchers() {
}); });
} }
/*
You can add annotations iff the add-on is on AND private browsing is off
*/
function canEnterAnnotations() {
return (annotatorIsOn && !privateBrowsing.isActive);
}
/* /*
Constructor for an Annotation object Constructor for an Annotation object
*/ */
@ -66,21 +58,17 @@ Function to tell the selector page mod that the add-on has become (in)active
function activateSelectors() { function activateSelectors() {
selectors.forEach( selectors.forEach(
function (selector) { function (selector) {
selector.postMessage(canEnterAnnotations()); selector.postMessage(annotatorIsOn);
}); });
} }
/* /*
Toggle activation: update the on/off state and notify the selectors. Toggle activation: update the on/off state and notify the selectors.
Toggling activation is disabled when private browsing is on.
*/ */
function toggleActivation() { function toggleActivation() {
if (privateBrowsing.isActive) {
return false;
}
annotatorIsOn = !annotatorIsOn; annotatorIsOn = !annotatorIsOn;
activateSelectors(); activateSelectors();
return canEnterAnnotations(); return annotatorIsOn;
} }
function detachWorker(worker, workerArray) { function detachWorker(worker, workerArray) {
@ -138,7 +126,7 @@ display it.
contentScriptFile: [data.url('jquery-1.4.2.min.js'), contentScriptFile: [data.url('jquery-1.4.2.min.js'),
data.url('selector.js')], data.url('selector.js')],
onAttach: function(worker) { onAttach: function(worker) {
worker.postMessage(canEnterAnnotations()); worker.postMessage(annotatorIsOn);
selectors.push(worker); selectors.push(worker);
worker.port.on('show', function(data) { worker.port.on('show', function(data) {
annotationEditor.annotationAnchor = data; annotationEditor.annotationAnchor = data;
@ -217,22 +205,6 @@ recent annotations until we are back in quota.
simpleStorage.storage.annotations.pop(); simpleStorage.storage.annotations.pop();
}); });
/*
We listen for private browsing start/stop events to change the widget icon
and to notify the selectors of the change in state.
*/
privateBrowsing.on('start', function() {
widget.contentURL = data.url('widget/pencil-off.png');
activateSelectors();
});
privateBrowsing.on('stop', function() {
if (canEnterAnnotations()) {
widget.contentURL = data.url('widget/pencil-on.png');
activateSelectors();
}
});
/* /*
The matcher page-mod locates anchors on web pages and prepares for the The matcher page-mod locates anchors on web pages and prepares for the
annotation to be displayed. annotation to be displayed.

View File

@ -224,7 +224,7 @@ const ContentWorker = Object.freeze({
}); });
}, },
injectMessageAPI: function injectMessageAPI(exports, pipe) { injectMessageAPI: function injectMessageAPI(exports, pipe, console) {
let { eventEmitter: port, emit : portEmit } = let { eventEmitter: port, emit : portEmit } =
ContentWorker.createEventEmitter(pipe.emit.bind(null, "event")); ContentWorker.createEventEmitter(pipe.emit.bind(null, "event"));
@ -293,7 +293,7 @@ const ContentWorker = Object.freeze({
ContentWorker.injectConsole(exports, pipe); ContentWorker.injectConsole(exports, pipe);
ContentWorker.injectTimers(exports, chromeAPI, pipe, exports.console); ContentWorker.injectTimers(exports, chromeAPI, pipe, exports.console);
ContentWorker.injectMessageAPI(exports, pipe); ContentWorker.injectMessageAPI(exports, pipe, exports.console);
if ( options !== undefined ) { if ( options !== undefined ) {
ContentWorker.injectOptions(exports, options); ContentWorker.injectOptions(exports, options);
} }

View File

@ -378,6 +378,13 @@ const Worker = EventEmitter.compose({
on: Trait.required, on: Trait.required,
_removeAllListeners: Trait.required, _removeAllListeners: Trait.required,
// List of messages fired before worker is initialized
get _earlyEvents() {
delete this._earlyEvents;
this._earlyEvents = [];
return this._earlyEvents;
},
/** /**
* Sends a message to the worker's global scope. Method takes single * Sends a message to the worker's global scope. Method takes single
* argument, which represents data to be sent to the worker. The data may * argument, which represents data to be sent to the worker. The data may
@ -390,13 +397,13 @@ const Worker = EventEmitter.compose({
* implementing `onMessage` function in the global scope of this worker. * implementing `onMessage` function in the global scope of this worker.
* @param {Number|String|JSON} data * @param {Number|String|JSON} data
*/ */
postMessage: function postMessage(data) { postMessage: function (data) {
if (!this._contentWorker) let args = ['message'].concat(Array.slice(arguments));
throw new Error(ERR_DESTROYED); if (!this._inited) {
if (this._frozen) this._earlyEvents.push(args);
throw new Error(ERR_FROZEN); return;
}
this._contentWorker.emit("message", data); processMessage.apply(this, args);
}, },
/** /**
@ -410,9 +417,8 @@ const Worker = EventEmitter.compose({
// before Worker.constructor gets called. (For ex: Panel) // before Worker.constructor gets called. (For ex: Panel)
// create an event emitter that receive and send events from/to the worker // create an event emitter that receive and send events from/to the worker
let self = this;
this._port = EventEmitterTrait.create({ this._port = EventEmitterTrait.create({
emit: function () self._emitEventToContent(Array.slice(arguments)) emit: this._emitEventToContent.bind(this)
}); });
// expose wrapped port, that exposes only public properties: // expose wrapped port, that exposes only public properties:
@ -438,24 +444,13 @@ const Worker = EventEmitter.compose({
* Emit a custom event to the content script, * Emit a custom event to the content script,
* i.e. emit this event on `self.port` * i.e. emit this event on `self.port`
*/ */
_emitEventToContent: function _emitEventToContent(args) { _emitEventToContent: function () {
// We need to save events that are emitted before the worker is let args = ['event'].concat(Array.slice(arguments));
// initialized
if (!this._inited) { if (!this._inited) {
this._earlyEvents.push(args); this._earlyEvents.push(args);
return; return;
} }
processMessage.apply(this, args);
if (this._frozen)
throw new Error(ERR_FROZEN);
// We throw exception when the worker has been destroyed
if (!this._contentWorker) {
throw new Error(ERR_DESTROYED);
}
// Forward the event to the WorkerSandbox object
this._contentWorker.emit.apply(null, ["event"].concat(args));
}, },
// Is worker connected to the content worker sandbox ? // Is worker connected to the content worker sandbox ?
@ -465,13 +460,6 @@ const Worker = EventEmitter.compose({
// Content script should not be reachable if frozen. // Content script should not be reachable if frozen.
_frozen: true, _frozen: true,
// List of custom events fired before worker is initialized
get _earlyEvents() {
delete this._earlyEvents;
this._earlyEvents = [];
return this._earlyEvents;
},
constructor: function Worker(options) { constructor: function Worker(options) {
options = options || {}; options = options || {};
@ -525,9 +513,11 @@ const Worker = EventEmitter.compose({
this._inited = true; this._inited = true;
this._frozen = false; this._frozen = false;
// Flush all events that have been fired before the worker is initialized. // Process all events and messages that were fired before the
this._earlyEvents.forEach((function (args) this._emitEventToContent(args)). // worker was initialized.
bind(this)); this._earlyEvents.forEach((function (args) {
processMessage.apply(this, args);
}).bind(this));
}, },
_documentUnload: function _documentUnload(subject, topic, data) { _documentUnload: function _documentUnload(subject, topic, data) {
@ -590,7 +580,7 @@ const Worker = EventEmitter.compose({
if (this._windowID) { if (this._windowID) {
this._windowID = null; this._windowID = null;
observers.remove("inner-window-destroyed", this._documentUnload); observers.remove("inner-window-destroyed", this._documentUnload);
this._earlyEvents.slice(0, this._earlyEvents.length); this._earlyEvents.length = 0;
this._emit("detach"); this._emit("detach");
} }
}, },
@ -622,4 +612,20 @@ const Worker = EventEmitter.compose({
*/ */
_injectInDocument: false _injectInDocument: false
}); });
/**
* Fired from postMessage and _emitEventToContent, or from the _earlyMessage
* queue when fired before the content is loaded. Sends arguments to
* contentWorker if able
*/
function processMessage () {
if (!this._contentWorker)
throw new Error(ERR_DESTROYED);
if (this._frozen)
throw new Error(ERR_FROZEN);
this._contentWorker.emit.apply(null, Array.slice(arguments));
}
exports.Worker = Worker; exports.Worker = Worker;

View File

@ -15,7 +15,7 @@ const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser,
getMostRecentBrowserWindow, getMostRecentWindow } = require('../window/utils'); getMostRecentBrowserWindow, getMostRecentWindow } = require('../window/utils');
const errors = require('../deprecated/errors'); const errors = require('../deprecated/errors');
const { deprecateFunction } = require('../util/deprecate'); const { deprecateFunction } = require('../util/deprecate');
const { ignoreWindow } = require('sdk/private-browsing/utils'); const { ignoreWindow, isGlobalPBSupported } = require('sdk/private-browsing/utils');
const { isPrivateBrowsingSupported } = require('../self'); const { isPrivateBrowsingSupported } = require('../self');
const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1']. const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
@ -24,7 +24,7 @@ const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
getService(Ci.nsIAppShellService); getService(Ci.nsIAppShellService);
// Bug 834961: ignore private windows when they are not supported // Bug 834961: ignore private windows when they are not supported
function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported }); function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported });
/** /**
* An iterator for XUL windows currently in the application. * An iterator for XUL windows currently in the application.

View File

@ -94,20 +94,23 @@ function emit(target, type, message /*, ...*/) {
* arguments. * arguments.
*/ */
emit.lazy = function lazy(target, type, message /*, ...*/) { emit.lazy = function lazy(target, type, message /*, ...*/) {
let args = Array.slice(arguments, 2) let args = Array.slice(arguments, 2);
let listeners = observers(target, type).slice() let listeners = observers(target, type).slice();
while (listeners.length) { let index = 0;
try { let count = listeners.length;
yield listeners.shift().apply(target, args);
} // If error event and there are no handlers then print error message
// into a console.
if (count === 0 && type === 'error') console.exception(message);
while (index < count) {
try { yield listeners[index].apply(target, args); }
catch (error) { catch (error) {
// If exception is not thrown by a error listener and error listener is // If exception is not thrown by a error listener and error listener is
// registered emit `error` event. Otherwise dump exception to the console. // registered emit `error` event. Otherwise dump exception to the console.
if (type !== 'error' && observers(target, 'error').length) if (type !== 'error') emit(target, 'error', error);
emit(target, 'error', error); else console.exception(error);
else
console.exception(error);
} }
index = index + 1;
} }
} }
exports.emit = emit; exports.emit = emit;

View File

@ -21,10 +21,11 @@ const { defer } = require("../core/promise");
const { when: unload } = require("../system/unload"); const { when: unload } = require("../system/unload");
const { validateOptions, getTypeOf } = require("../deprecated/api-utils"); const { validateOptions, getTypeOf } = require("../deprecated/api-utils");
const { window } = require("../addon/window"); const { window } = require("../addon/window");
const { fromIterator } = require("../util/array");
// This cache is used to access friend properties between functions // This cache is used to access friend properties between functions
// without exposing them on the public API. // without exposing them on the public API.
let cache = []; let cache = new Set();
let elements = new WeakMap(); let elements = new WeakMap();
function contentLoaded(target) { function contentLoaded(target) {
@ -75,20 +76,13 @@ var HiddenFrame = Class({
}); });
exports.HiddenFrame = HiddenFrame exports.HiddenFrame = HiddenFrame
function isFrameCached(frame) {
// Function returns `true` if frame was already cached.
return cache.some(function(value) {
return value === frame
})
}
function addHidenFrame(frame) { function addHidenFrame(frame) {
if (!(frame instanceof HiddenFrame)) if (!(frame instanceof HiddenFrame))
throw Error("The object to be added must be a HiddenFrame."); throw Error("The object to be added must be a HiddenFrame.");
// This instance was already added. // This instance was already added.
if (isFrameCached(frame)) return frame; if (cache.has(frame)) return frame;
else cache.push(frame); else cache.add(frame);
let element = makeFrame(window.document, { let element = makeFrame(window.document, {
nodeName: "iframe", nodeName: "iframe",
@ -111,14 +105,14 @@ function removeHiddenFrame(frame) {
if (!(frame instanceof HiddenFrame)) if (!(frame instanceof HiddenFrame))
throw Error("The object to be removed must be a HiddenFrame."); throw Error("The object to be removed must be a HiddenFrame.");
if (!isFrameCached(frame)) return; if (!cache.has(frame)) return;
// Remove from cache before calling in order to avoid loop // Remove from cache before calling in order to avoid loop
cache.splice(cache.indexOf(frame), 1); cache.delete(frame);
emit(frame, "unload") emit(frame, "unload")
let element = frame.element let element = frame.element
if (element) element.parentNode.removeChild(element) if (element) element.parentNode.removeChild(element)
} }
exports.remove = removeHiddenFrame; exports.remove = removeHiddenFrame;
unload(function() cache.splice(0).forEach(removeHiddenFrame)); unload(function() fromIterator(cache).forEach(removeHiddenFrame));

View File

@ -17,16 +17,20 @@ const { validateOptions: valid } = require('./deprecated/api-utils');
const { Symbiont } = require('./content/content'); const { Symbiont } = require('./content/content');
const { EventEmitter } = require('./deprecated/events'); const { EventEmitter } = require('./deprecated/events');
const { setTimeout } = require('./timers'); const { setTimeout } = require('./timers');
const { on, off, emit } = require('./system/events');
const runtime = require('./system/runtime'); const runtime = require('./system/runtime');
const { getDocShell } = require("./frame/utils"); const { getDocShell } = require("./frame/utils");
const { getWindow } = require('./panel/window'); const { getWindow } = require('./panel/window');
const { isPrivateBrowsingSupported } = require('./self'); const { isPrivateBrowsingSupported } = require('./self');
const { isWindowPBSupported } = require('./private-browsing/utils'); const { isWindowPBSupported } = require('./private-browsing/utils');
const { getNodeView } = require('./view/core');
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
ON_SHOW = 'popupshown', ON_SHOW = 'popupshown',
ON_HIDE = 'popuphidden', ON_HIDE = 'popuphidden',
validNumber = { is: ['number', 'undefined', 'null'] }; validNumber = { is: ['number', 'undefined', 'null'] },
validBoolean = { is: ['boolean', 'undefined', 'null'] },
ADDON_ID = require('./self').id;
if (isPrivateBrowsingSupported && isWindowPBSupported) { if (isPrivateBrowsingSupported && isWindowPBSupported) {
throw Error('The panel module cannot be used with per-window private browsing at the moment, see Bug 816257'); throw Error('The panel module cannot be used with per-window private browsing at the moment, see Bug 816257');
@ -68,6 +72,9 @@ const Panel = Symbiont.resolve({
constructor: function Panel(options) { constructor: function Panel(options) {
this._onShow = this._onShow.bind(this); this._onShow = this._onShow.bind(this);
this._onHide = this._onHide.bind(this); this._onHide = this._onHide.bind(this);
this._onAnyPanelShow = this._onAnyPanelShow.bind(this);
on('sdk-panel-show', this._onAnyPanelShow);
this.on('inited', this._onSymbiontInit.bind(this)); this.on('inited', this._onSymbiontInit.bind(this));
this.on('propertyChange', this._onChange.bind(this)); this.on('propertyChange', this._onChange.bind(this));
@ -82,6 +89,12 @@ const Panel = Symbiont.resolve({
this.height = options.height; this.height = options.height;
if ('contentURL' in options) if ('contentURL' in options)
this.contentURL = options.contentURL; this.contentURL = options.contentURL;
if ('focus' in options) {
var value = options.focus;
var validatedValue = valid({ $: value }, { $: validBoolean }).$;
this._focus =
(typeof validatedValue == 'boolean') ? validatedValue : this._focus;
}
this._init(options); this._init(options);
}, },
@ -91,6 +104,7 @@ const Panel = Symbiont.resolve({
this._removeAllListeners('hide'); this._removeAllListeners('hide');
this._removeAllListeners('propertyChange'); this._removeAllListeners('propertyChange');
this._removeAllListeners('inited'); this._removeAllListeners('inited');
off('sdk-panel-show', this._onAnyPanelShow);
// defer cleanup to be performed after panel gets hidden // defer cleanup to be performed after panel gets hidden
this._xulPanel = null; this._xulPanel = null;
this._symbiontDestructor(this); this._symbiontDestructor(this);
@ -109,13 +123,16 @@ const Panel = Symbiont.resolve({
set height(value) set height(value)
this._height = valid({ $: value }, { $: validNumber }).$ || this._height, this._height = valid({ $: value }, { $: validNumber }).$ || this._height,
_height: 240, _height: 240,
/* Public API: Panel.focus */
get focus() this._focus,
_focus: true,
/* Public API: Panel.isShowing */ /* Public API: Panel.isShowing */
get isShowing() !!this._xulPanel && this._xulPanel.state == "open", get isShowing() !!this._xulPanel && this._xulPanel.state == "open",
/* Public API: Panel.show */ /* Public API: Panel.show */
show: function show(anchor) { show: function show(anchor) {
anchor = anchor || null; anchor = anchor ? getNodeView(anchor) : null;
let anchorWindow = getWindow(anchor); let anchorWindow = getWindow(anchor);
// If there is no open window, or the anchor is in a private window // If there is no open window, or the anchor is in a private window
@ -126,6 +143,7 @@ const Panel = Symbiont.resolve({
let document = anchorWindow.document; let document = anchorWindow.document;
let xulPanel = this._xulPanel; let xulPanel = this._xulPanel;
let panel = this;
if (!xulPanel) { if (!xulPanel) {
xulPanel = this._xulPanel = document.createElementNS(XUL_NS, 'panel'); xulPanel = this._xulPanel = document.createElementNS(XUL_NS, 'panel');
xulPanel.setAttribute("type", "arrow"); xulPanel.setAttribute("type", "arrow");
@ -165,7 +183,7 @@ const Panel = Symbiont.resolve({
xulPanel.appendChild(frame); xulPanel.appendChild(frame);
document.getElementById("mainPopupSet").appendChild(xulPanel); document.getElementById("mainPopupSet").appendChild(xulPanel);
} }
let { width, height } = this, x, y, position; let { width, height, focus } = this, x, y, position;
if (!anchor) { if (!anchor) {
// Open the popup in the middle of the window. // Open the popup in the middle of the window.
@ -210,13 +228,25 @@ const Panel = Symbiont.resolve({
xulPanel.firstChild.style.width = width + "px"; xulPanel.firstChild.style.width = width + "px";
xulPanel.firstChild.style.height = height + "px"; xulPanel.firstChild.style.height = height + "px";
// Only display xulPanel if Panel.hide() was not called
// after Panel.show(), but before xulPanel.openPopup
// was loaded
emit('sdk-panel-show', { data: ADDON_ID, subject: xulPanel });
// Prevent the panel from getting focus when showing up
// if focus is set to false
xulPanel.setAttribute("noautofocus",!focus);
// Wait for the XBL binding to be constructed // Wait for the XBL binding to be constructed
function waitForBinding() { function waitForBinding() {
if (!xulPanel.openPopup) { if (!xulPanel.openPopup) {
setTimeout(waitForBinding, 50); setTimeout(waitForBinding, 50);
return; return;
} }
xulPanel.openPopup(anchor, position, x, y);
if (xulPanel.state !== 'hiding') {
xulPanel.openPopup(anchor, position, x, y);
}
} }
waitForBinding(); waitForBinding();
@ -289,6 +319,8 @@ const Panel = Symbiont.resolve({
* text color. * text color.
*/ */
_applyStyleToDocument: function _applyStyleToDocument() { _applyStyleToDocument: function _applyStyleToDocument() {
if (this._defaultStyleApplied)
return;
try { try {
let win = this._xulPanel.ownerDocument.defaultView; let win = this._xulPanel.ownerDocument.defaultView;
let node = win.document.getAnonymousElementByAttribute( let node = win.document.getAnonymousElementByAttribute(
@ -309,6 +341,7 @@ const Panel = Symbiont.resolve({
container.insertBefore(style, container.firstChild); container.insertBefore(style, container.firstChild);
else else
container.appendChild(style); container.appendChild(style);
this._defaultStyleApplied = true;
} }
catch(e) { catch(e) {
console.error("Unable to apply panel style"); console.error("Unable to apply panel style");
@ -333,6 +366,16 @@ const Panel = Symbiont.resolve({
this._emit('error', e); this._emit('error', e);
} }
}, },
/**
* When any panel is displayed, system-wide, close `this`
* panel unless it's the most recently displayed panel
*/
_onAnyPanelShow: function _onAnyPanelShow(e) {
if (e.subject !== this._xulPanel)
this.hide();
},
/** /**
* Notification that panel was fully initialized. * Notification that panel was fully initialized.
*/ */

View File

@ -6,11 +6,12 @@
const { getMostRecentBrowserWindow, windows: getWindows } = require('../window/utils'); const { getMostRecentBrowserWindow, windows: getWindows } = require('../window/utils');
const { ignoreWindow } = require('../private-browsing/utils'); const { ignoreWindow } = require('../private-browsing/utils');
const { isPrivateBrowsingSupported } = require('../self'); const { isPrivateBrowsingSupported } = require('../self');
const { isGlobalPBSupported } = require('../private-browsing/utils');
function getWindow(anchor) { function getWindow(anchor) {
let window; let window;
let windows = getWindows("navigator:browser", { let windows = getWindows("navigator:browser", {
includePrivate: isPrivateBrowsingSupported includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported
}); });
if (anchor) { if (anchor) {

View File

@ -54,7 +54,7 @@ let isTabPBSupported = exports.isTabPBSupported =
!pbService && !!PrivateBrowsingUtils && is('Fennec') && satisfiesVersion(version, '>=20.0*'); !pbService && !!PrivateBrowsingUtils && is('Fennec') && satisfiesVersion(version, '>=20.0*');
function ignoreWindow(window) { function ignoreWindow(window) {
return !isPrivateBrowsingSupported && isWindowPrivate(window); return !isPrivateBrowsingSupported && isWindowPrivate(window) && !isGlobalPBSupported;
} }
exports.ignoreWindow = ignoreWindow; exports.ignoreWindow = ignoreWindow;

View File

@ -7,7 +7,7 @@ const { Cc, Ci } = require('chrome');
const { Class } = require('../core/heritage'); const { Class } = require('../core/heritage');
const { tabNS, rawTabNS } = require('./namespace'); const { tabNS, rawTabNS } = require('./namespace');
const { EventTarget } = require('../event/target'); const { EventTarget } = require('../event/target');
const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL, getContentWindowForTab, const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL, getTabContentWindow,
getTabForBrowser, getTabForBrowser,
setTabURL, getOwnerWindow, getTabContentType, getTabId } = require('./utils'); setTabURL, getOwnerWindow, getTabContentType, getTabId } = require('./utils');
const { emit } = require('../event/core'); const { emit } = require('../event/core');
@ -129,7 +129,7 @@ const Tab = Class({
// BUG 792946 https://bugzilla.mozilla.org/show_bug.cgi?id=792946 // BUG 792946 https://bugzilla.mozilla.org/show_bug.cgi?id=792946
// TODO: fix this circular dependency // TODO: fix this circular dependency
let { Worker } = require('./worker'); let { Worker } = require('./worker');
return Worker(options, tabNS(this).tab.browser.contentWindow); return Worker(options, getTabContentWindow(tabNS(this).tab));
}, },
/** /**
@ -194,5 +194,5 @@ function onTabClose(event) {
}; };
getPBOwnerWindow.define(Tab, function(tab) { getPBOwnerWindow.define(Tab, function(tab) {
return getContentWindowForTab(tabNS(tab).tab); return getTabContentWindow(tabNS(tab).tab);
}); });

View File

@ -16,9 +16,10 @@ const { Ci } = require('chrome');
const { defer } = require("../lang/functional"); const { defer } = require("../lang/functional");
const { windows, isBrowser } = require('../window/utils'); const { windows, isBrowser } = require('../window/utils');
const { isPrivateBrowsingSupported } = require('../self'); const { isPrivateBrowsingSupported } = require('../self');
const { isGlobalPBSupported } = require('../private-browsing/utils');
// Bug 834961: ignore private windows when they are not supported // Bug 834961: ignore private windows when they are not supported
function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported }); function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported });
function activateTab(tab, window) { function activateTab(tab, window) {
let gBrowser = getTabBrowserForTab(tab); let gBrowser = getTabBrowserForTab(tab);
@ -166,12 +167,6 @@ function getBrowserForTab(tab) {
} }
exports.getBrowserForTab = getBrowserForTab; exports.getBrowserForTab = getBrowserForTab;
function getContentWindowForTab(tab) {
return getBrowserForTab(tab).contentWindow;
}
exports.getContentWindowForTab = getContentWindowForTab;
function getTabId(tab) { function getTabId(tab) {
if (tab.browser) // fennec if (tab.browser) // fennec
return tab.id return tab.id

View File

@ -51,7 +51,11 @@ exports.LoaderWithHookedConsole = function (module, callback) {
warn: hook.bind("warn"), warn: hook.bind("warn"),
error: hook.bind("error"), error: hook.bind("error"),
debug: hook.bind("debug"), debug: hook.bind("debug"),
exception: hook.bind("exception") exception: hook.bind("exception"),
__exposedProps__: {
log: "rw", info: "rw", warn: "rw", error: "rw", debug: "rw",
exception: "rw"
}
} }
}), }),
messages: messages messages: messages

View File

@ -90,8 +90,14 @@ exports.flatten = function flatten(array){
function fromIterator(iterator) { function fromIterator(iterator) {
let array = []; let array = [];
for each (let item in iterator) if (iterator.__iterator__) {
array.push(item); for each (let item in iterator)
array.push(item);
}
else {
for (let item of iterator)
array.push(item);
}
return array; return array;
} }
exports.fromIterator = fromIterator; exports.fromIterator = fromIterator;

View File

@ -0,0 +1,46 @@
/* 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"
};
var { Ci } = require("chrome");
/**
Temporarily emulate method so we don't have to uplift whole method
implementation.
var method = require("method/core");
// Returns DOM node associated with a view for
// the given `value`. If `value` has no view associated
// it returns `null`. You can implement this method for
// this type to define what the result should be for it.
let getNodeView = method("getNodeView");
getNodeView.define(function(value) {
if (value instanceof Ci.nsIDOMNode)
return value;
return null;
});
**/
let implementations = new WeakMap();
function getNodeView(value) {
if (value instanceof Ci.nsIDOMNode)
return value;
if (implementations.has(value))
return implementations.get(value)(value);
return null;
}
getNodeView.implement = function(value, implementation) {
implementations.set(value, implementation)
}
exports.getNodeView = getNodeView;

View File

@ -48,6 +48,7 @@ const { isBrowser } = require("./window/utils");
const { setTimeout } = require("./timers"); const { setTimeout } = require("./timers");
const unload = require("./system/unload"); const unload = require("./system/unload");
const { uuid } = require("./util/uuid"); const { uuid } = require("./util/uuid");
const { getNodeView } = require("./view/core");
// Data types definition // Data types definition
const valid = { const valid = {
@ -362,7 +363,6 @@ const Widget = function Widget(options) {
exports.Widget = Widget; exports.Widget = Widget;
/** /**
* WidgetView is an instance of a widget for a specific window. * WidgetView is an instance of a widget for a specific window.
* *
@ -432,7 +432,10 @@ const WidgetViewTrait = LightTrait.compose(EventEmitterTrait, LightTrait({
// Special case for click events: if the widget doesn't have a click // Special case for click events: if the widget doesn't have a click
// handler, but it does have a panel, display the panel. // handler, but it does have a panel, display the panel.
if ("click" == type && !this._listeners("click").length && this.panel) if ("click" == type && !this._listeners("click").length && this.panel)
this.panel.show(domNode); // This kind of ugly workaround, instead we should implement
// `getNodeView` for the `Widget` class itself, but that's kind of
// hard without cleaning things up.
this.panel.show(getNodeView.implement({}, function() domNode));
}, },
_isInWindow: function WidgetView__isInWindow(window) { _isInWindow: function WidgetView__isInWindow(window) {
@ -472,6 +475,7 @@ const WidgetViewTrait = LightTrait.compose(EventEmitterTrait, LightTrait({
})); }));
const WidgetView = function WidgetView(baseWidget) { const WidgetView = function WidgetView(baseWidget) {
let w = WidgetViewTrait.create(WidgetView.prototype); let w = WidgetViewTrait.create(WidgetView.prototype);
w._initWidgetView(baseWidget); w._initWidgetView(baseWidget);
@ -479,7 +483,6 @@ const WidgetView = function WidgetView(baseWidget) {
} }
/** /**
* Keeps track of all browser windows. * Keeps track of all browser windows.
* Exposes methods for adding/removing widgets * Exposes methods for adding/removing widgets

View File

@ -9,7 +9,7 @@ const { on, off, once } = require('../event/core');
const { method } = require('../lang/functional'); const { method } = require('../lang/functional');
const { getWindowTitle } = require('./utils'); const { getWindowTitle } = require('./utils');
const unload = require('../system/unload'); const unload = require('../system/unload');
const { isWindowPrivate } = require('../private-browsing/utils'); const { isWindowPrivate } = require('../window/utils');
const { EventTarget } = require('../event/target'); const { EventTarget } = require('../event/target');
const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils'); const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils');
const { deprecateUsage } = require('../util/deprecate'); const { deprecateUsage } = require('../util/deprecate');

View File

@ -5,9 +5,8 @@
const { Class } = require('../core/heritage'); const { Class } = require('../core/heritage');
const { BrowserWindow } = require('../window/browser'); const { BrowserWindow } = require('../window/browser');
const windowUtils = require('../deprecated/window-utils'); const { WindowTracker } = require('../deprecated/window-utils');
const { WindowTracker } = windowUtils; const { isBrowser, getMostRecentBrowserWindow } = require('../window/utils');
const { isBrowser } = require('../window/utils');
const { windowNS } = require('../window/namespace'); const { windowNS } = require('../window/namespace');
const { on, off, once, emit } = require('../event/core'); const { on, off, once, emit } = require('../event/core');
const { method } = require('../lang/functional'); const { method } = require('../lang/functional');
@ -25,7 +24,7 @@ let BrowserWindows = Class({
List.prototype.initialize.apply(this); List.prototype.initialize.apply(this);
}, },
get activeWindow() { get activeWindow() {
let window = windowUtils.activeBrowserWindow; let window = getMostRecentBrowserWindow();
return window ? getBrowserWindow({window: window}) : null; return window ? getBrowserWindow({window: window}) : null;
}, },
open: function open(options) { open: function open(options) {

View File

@ -8,7 +8,8 @@ const { Tab } = require('../tabs/tab');
const { browserWindows } = require('./fennec'); const { browserWindows } = require('./fennec');
const { windowNS } = require('../window/namespace'); const { windowNS } = require('../window/namespace');
const { tabsNS, tabNS } = require('../tabs/namespace'); const { tabsNS, tabNS } = require('../tabs/namespace');
const { openTab, getTabs, getSelectedTab, getTabForBrowser: getRawTabForBrowser } = require('../tabs/utils'); const { openTab, getTabs, getSelectedTab, getTabForBrowser: getRawTabForBrowser,
getTabContentWindow } = require('../tabs/utils');
const { Options } = require('../tabs/common'); const { Options } = require('../tabs/common');
const { getTabForBrowser, getTabForRawTab } = require('../tabs/helpers'); const { getTabForBrowser, getTabForRawTab } = require('../tabs/helpers');
const { on, once, off, emit } = require('../event/core'); const { on, once, off, emit } = require('../event/core');
@ -18,8 +19,8 @@ const { EventTarget } = require('../event/target');
const { when: unload } = require('../system/unload'); const { when: unload } = require('../system/unload');
const { windowIterator } = require('../deprecated/window-utils'); const { windowIterator } = require('../deprecated/window-utils');
const { List, addListItem, removeListItem } = require('../util/list'); const { List, addListItem, removeListItem } = require('../util/list');
const { isPrivateBrowsingSupported } = require('sdk/self'); const { isPrivateBrowsingSupported } = require('../self');
const { isTabPBSupported } = require('sdk/private-browsing/utils'); const { isTabPBSupported, ignoreWindow } = require('../private-browsing/utils');
const mainWindow = windowNS(browserWindows.activeWindow).window; const mainWindow = windowNS(browserWindows.activeWindow).window;
@ -113,6 +114,10 @@ function removeTab(tab) {
function onTabOpen(event) { function onTabOpen(event) {
let browser = event.target; let browser = event.target;
// Eventually ignore private tabs
if (ignoreWindow(browser.contentWindow))
return;
let tab = getTabForBrowser(browser); let tab = getTabForBrowser(browser);
if (tab === null) { if (tab === null) {
let rawTab = getRawTabForBrowser(browser); let rawTab = getRawTabForBrowser(browser);
@ -132,8 +137,14 @@ function onTabOpen(event) {
// TabSelect // TabSelect
function onTabSelect(event) { function onTabSelect(event) {
let browser = event.target;
// Eventually ignore private tabs
if (ignoreWindow(browser.contentWindow))
return;
// Set value whenever new tab becomes active. // Set value whenever new tab becomes active.
let tab = getTabForBrowser(event.target); let tab = getTabForBrowser(browser);
emit(tab, 'activate', tab); emit(tab, 'activate', tab);
emit(gTabs, 'activate', tab); emit(gTabs, 'activate', tab);

View File

@ -16,7 +16,7 @@ const { getOwnerWindow, getActiveTab, getTabs,
openTab } = require("../tabs/utils"); openTab } = require("../tabs/utils");
const { Options } = require("../tabs/common"); const { Options } = require("../tabs/common");
const { observer: tabsObserver } = require("../tabs/observer"); const { observer: tabsObserver } = require("../tabs/observer");
const { ignoreWindow, isWindowPrivate } = require("../private-browsing/utils"); const { ignoreWindow } = require("../private-browsing/utils");
const TAB_BROWSER = "tabbrowser"; const TAB_BROWSER = "tabbrowser";

View File

@ -11,6 +11,9 @@ DEFAULT_COMMON_PREFS = {
'javascript.options.strict': True, 'javascript.options.strict': True,
'javascript.options.showInConsole': True, 'javascript.options.showInConsole': True,
# Allow remote connections to the debugger
'devtools.debugger.remote-enabled' : True,
'extensions.sdk.console.logLevel': 'info', 'extensions.sdk.console.logLevel': 'info',
'extensions.checkCompatibility.nightly' : False, 'extensions.checkCompatibility.nightly' : False,
@ -51,6 +54,7 @@ DEFAULT_FIREFOX_PREFS = {
'browser.startup.homepage' : 'about:blank', 'browser.startup.homepage' : 'about:blank',
'startup.homepage_welcome_url' : 'about:blank', 'startup.homepage_welcome_url' : 'about:blank',
'devtools.errorconsole.enabled' : True, 'devtools.errorconsole.enabled' : True,
'devtools.chrome.enabled' : True,
# Disable the feedback extension # Disable the feedback extension
'extensions.testpilot.runStudies' : False, 'extensions.testpilot.runStudies' : False,

View File

@ -165,11 +165,11 @@ def gen_manifest(template_root_dir, target_cfg, jid,
ta_desc.appendChild(elem) ta_desc.appendChild(elem)
elem = dom.createElement("em:minVersion") elem = dom.createElement("em:minVersion")
elem.appendChild(dom.createTextNode("18.0")) elem.appendChild(dom.createTextNode("19.0"))
ta_desc.appendChild(elem) ta_desc.appendChild(elem)
elem = dom.createElement("em:maxVersion") elem = dom.createElement("em:maxVersion")
elem.appendChild(dom.createTextNode("21.0a1")) elem.appendChild(dom.createTextNode("22.0a1"))
ta_desc.appendChild(elem) ta_desc.appendChild(elem)
if target_cfg.get("homepage"): if target_cfg.get("homepage"):

View File

@ -141,16 +141,15 @@ class TestInit(unittest.TestCase):
class TestCfxQuits(unittest.TestCase): class TestCfxQuits(unittest.TestCase):
def run_cfx(self, addon_name, command): def run_cfx(self, addon_path, command):
old_cwd = os.getcwd() old_cwd = os.getcwd()
addon_path = os.path.join(tests_path,
"addons", addon_name)
os.chdir(addon_path) os.chdir(addon_path)
import sys import sys
old_stdout = sys.stdout old_stdout = sys.stdout
old_stderr = sys.stderr old_stderr = sys.stderr
sys.stdout = out = StringIO() sys.stdout = out = StringIO()
sys.stderr = err = StringIO() sys.stderr = err = StringIO()
rc = 0
try: try:
import cuddlefish import cuddlefish
args = list(command) args = list(command)
@ -182,17 +181,45 @@ class TestCfxQuits(unittest.TestCase):
container) container)
self.fail(standardMsg) self.fail(standardMsg)
def test_run(self): def test_cfx_run(self):
rc, out, err = self.run_cfx("simplest-test", ["run"]) addon_path = os.path.join(tests_path,
"addons", "simplest-test")
rc, out, err = self.run_cfx(addon_path, ["run"])
self.assertEqual(rc, 0) self.assertEqual(rc, 0)
self.assertIn("Program terminated successfully.", err) self.assertIn("Program terminated successfully.", err)
def test_test(self): def test_cfx_test(self):
rc, out, err = self.run_cfx("simplest-test", ["test"]) addon_path = os.path.join(tests_path,
"addons", "simplest-test")
rc, out, err = self.run_cfx(addon_path, ["test"])
self.assertEqual(rc, 0) self.assertEqual(rc, 0)
self.assertIn("1 of 1 tests passed.", err) self.assertIn("1 of 1 tests passed.", err)
self.assertIn("Program terminated successfully.", err) self.assertIn("Program terminated successfully.", err)
def test_cfx_init(self):
# Create an empty test directory
addon_path = os.path.abspath(os.path.join(".test_tmp", "test-cfx-init"))
if os.path.isdir(addon_path):
shutil.rmtree(addon_path)
os.makedirs(addon_path)
# Fake a call to cfx init
old_cwd = os.getcwd()
os.chdir(addon_path)
out, err = StringIO(), StringIO()
rc = initializer(None, ["init"], out, err)
os.chdir(old_cwd)
out, err = out.getvalue(), err.getvalue()
self.assertEqual(rc["result"], 0)
self.assertTrue("Have fun!" in out)
self.assertEqual(err,"")
# run cfx test
rc, out, err = self.run_cfx(addon_path, ["test"])
self.assertEqual(rc, 0)
self.assertIn("2 of 2 tests passed.", err)
self.assertIn("Program terminated successfully.", err)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict"; "use strict";
const app = require("sdk/system/xul-app");
// This test makes sure that require statements used by all AMO hosted // This test makes sure that require statements used by all AMO hosted
// add-ons will be able to use old require statements. // add-ons will be able to use old require statements.
@ -16,14 +17,18 @@ exports["test compatibility"] = function(assert) {
assert.equal(require("tabs"), assert.equal(require("tabs"),
require("sdk/tabs"), "sdk/tabs -> tabs"); require("sdk/tabs"), "sdk/tabs -> tabs");
assert.equal(require("widget"), if (app.is("Firefox")) {
require("sdk/widget"), "sdk/widget -> widget"); assert.equal(require("widget"),
require("sdk/widget"), "sdk/widget -> widget");
}
assert.equal(require("page-mod"), assert.equal(require("page-mod"),
require("sdk/page-mod"), "sdk/page-mod -> page-mod"); require("sdk/page-mod"), "sdk/page-mod -> page-mod");
assert.equal(require("panel"), if (app.is("Firefox")) {
require("sdk/panel"), "sdk/panel -> panel"); assert.equal(require("panel"),
require("sdk/panel"), "sdk/panel -> panel");
}
assert.equal(require("request"), assert.equal(require("request"),
require("sdk/request"), "sdk/request -> request"); require("sdk/request"), "sdk/request -> request");
@ -34,8 +39,10 @@ exports["test compatibility"] = function(assert) {
assert.equal(require("simple-storage"), assert.equal(require("simple-storage"),
require("sdk/simple-storage"), "sdk/simple-storage -> simple-storage"); require("sdk/simple-storage"), "sdk/simple-storage -> simple-storage");
assert.equal(require("context-menu"), if (app.is("Firefox")) {
require("sdk/context-menu"), "sdk/context-menu -> context-menu"); assert.equal(require("context-menu"),
require("sdk/context-menu"), "sdk/context-menu -> context-menu");
}
assert.equal(require("notifications"), assert.equal(require("notifications"),
require("sdk/notifications"), "sdk/notifications -> notifications"); require("sdk/notifications"), "sdk/notifications -> notifications");
@ -49,8 +56,10 @@ exports["test compatibility"] = function(assert) {
assert.equal(require("url"), assert.equal(require("url"),
require("sdk/url"), "sdk/url -> url"); require("sdk/url"), "sdk/url -> url");
assert.equal(require("selection"), if (app.is("Firefox")) {
require("sdk/selection"), "sdk/selection -> selection"); assert.equal(require("selection"),
require("sdk/selection"), "sdk/selection -> selection");
}
assert.equal(require("timers"), assert.equal(require("timers"),
require("sdk/timers"), "sdk/timers -> timers"); require("sdk/timers"), "sdk/timers -> timers");
@ -97,8 +106,10 @@ exports["test compatibility"] = function(assert) {
assert.equal(require("match-pattern"), assert.equal(require("match-pattern"),
require("sdk/page-mod/match-pattern"), "sdk/page-mod/match-pattern -> match-pattern"); require("sdk/page-mod/match-pattern"), "sdk/page-mod/match-pattern -> match-pattern");
assert.equal(require("tab-browser"), if (app.is("Firefox")) {
require("sdk/deprecated/tab-browser"), "sdk/deprecated/tab-browser -> tab-browser"); assert.equal(require("tab-browser"),
require("sdk/deprecated/tab-browser"), "sdk/deprecated/tab-browser -> tab-browser");
}
assert.equal(require("file"), assert.equal(require("file"),
require("sdk/io/file"), "sdk/io/file -> file"); require("sdk/io/file"), "sdk/io/file -> file");
@ -154,8 +165,12 @@ exports["test compatibility"] = function(assert) {
assert.equal(require("environment"), assert.equal(require("environment"),
require("sdk/system/environment"), "sdk/system/environment -> environment"); require("sdk/system/environment"), "sdk/system/environment -> environment");
assert.equal(require("utils/data"), if (app.is("Firefox")) {
require("sdk/io/data"), "sdk/io/data -> utils/data"); // This module fails on fennec because of favicon xpcom component
// being not implemented on it.
assert.equal(require("utils/data"),
require("sdk/io/data"), "sdk/io/data -> utils/data");
}
assert.equal(require("test/assert"), assert.equal(require("test/assert"),
require("sdk/test/assert"), "sdk/test/assert -> test/assert"); require("sdk/test/assert"), "sdk/test/assert -> test/assert");
@ -174,14 +189,4 @@ exports["test compatibility"] = function(assert) {
"api-utils/cortex -> sdk/deprecated/cortex"); "api-utils/cortex -> sdk/deprecated/cortex");
}; };
if (require("sdk/system/xul-app").is("Fennec")) {
module.exports = {
"test Unsupported Test": function UnsupportedTest (assert) {
assert.pass(
"Skipping this test until Fennec support is implemented." +
"See bug 809352");
}
}
}
require("sdk/test/runner").runTestsFromModule(module); require("sdk/test/runner").runTestsFromModule(module);

View File

@ -3,173 +3,22 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict'; 'use strict';
const { Ci } = require('chrome');
const { isPrivateBrowsingSupported } = require('sdk/self');
const tabs = require('sdk/tabs');
const { browserWindows: windows } = require('sdk/windows');
const { isPrivate } = require('sdk/private-browsing');
const { getOwnerWindow } = require('sdk/private-browsing/window/utils');
const { is } = require('sdk/system/xul-app');
const { isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
const { merge } = require('sdk/util/object'); const { merge } = require('sdk/util/object');
const app = require("sdk/system/xul-app");
const TAB_URL = 'data:text/html;charset=utf-8,TEST-TAB'; const { isGlobalPBSupported } = require('sdk/private-browsing/utils');
exports.testIsPrivateBrowsingTrue = function(assert) {
assert.ok(isPrivateBrowsingSupported,
'isPrivateBrowsingSupported property is true');
};
// test tab.open with isPrivate: true
// test isPrivate on a tab
// test getOwnerWindow on windows and tabs
exports.testGetOwnerWindow = function(assert, done) {
let window = windows.activeWindow;
let chromeWindow = getOwnerWindow(window);
assert.ok(chromeWindow instanceof Ci.nsIDOMWindow, 'associated window is found');
tabs.open({
url: 'about:blank',
isPrivate: true,
onOpen: function(tab) {
// test that getOwnerWindow works as expected
if (is('Fennec')) {
assert.notStrictEqual(chromeWindow, getOwnerWindow(tab));
assert.ok(getOwnerWindow(tab) instanceof Ci.nsIDOMWindow);
}
else {
if (isWindowPBSupported) {
assert.notStrictEqual(chromeWindow,
getOwnerWindow(tab),
'associated window is not the same for window and window\'s tab');
}
else {
assert.strictEqual(chromeWindow,
getOwnerWindow(tab),
'associated window is the same for window and window\'s tab');
}
}
let pbSupported = isTabPBSupported || isWindowPBSupported;
// test that the tab is private if it should be
assert.equal(isPrivate(tab), pbSupported);
assert.equal(isPrivate(getOwnerWindow(tab)), pbSupported);
tab.close(function() done());
}
});
};
// test that it is possible to open a private tab
exports.testTabOpenPrivate = function(assert, done) {
tabs.open({
url: TAB_URL,
isPrivate: true,
onReady: function(tab) {
assert.equal(tab.url, TAB_URL, 'opened correct tab');
assert.equal(isPrivate(tab), (isWindowPBSupported || isTabPBSupported));
tab.close(function() {
done();
});
}
});
}
// test that it is possible to open a non private tab
exports.testTabOpenPrivateDefault = function(assert, done) {
tabs.open({
url: TAB_URL,
onReady: function(tab) {
assert.equal(tab.url, TAB_URL, 'opened correct tab');
assert.equal(isPrivate(tab), false);
tab.close(function() {
done();
});
}
});
}
// test that it is possible to open a non private tab in explicit case
exports.testTabOpenPrivateOffExplicit = function(assert, done) {
tabs.open({
url: TAB_URL,
isPrivate: false,
onReady: function(tab) {
assert.equal(tab.url, TAB_URL, 'opened correct tab');
assert.equal(isPrivate(tab), false);
tab.close(function() {
done();
});
}
});
}
// test windows.open with isPrivate: true
// test isPrivate on a window
if (!is('Fennec')) {
// test that it is possible to open a private window
exports.testWindowOpenPrivate = function(assert, done) {
windows.open({
url: TAB_URL,
isPrivate: true,
onOpen: function(window) {
let tab = window.tabs[0];
tab.once('ready', function() {
assert.equal(tab.url, TAB_URL, 'opened correct tab');
assert.equal(isPrivate(tab), isWindowPBSupported, 'tab is private');
window.close(function() {
done();
});
});
}
});
};
exports.testIsPrivateOnWindowOn = function(assert, done) {
windows.open({
isPrivate: true,
onOpen: function(window) {
assert.equal(isPrivate(window), isWindowPBSupported, 'isPrivate for a window is true when it should be');
assert.equal(isPrivate(window.tabs[0]), isWindowPBSupported, 'isPrivate for a tab is false when it should be');
window.close(done);
}
});
};
exports.testIsPrivateOnWindowOffImplicit = function(assert, done) {
windows.open({
onOpen: function(window) {
assert.equal(isPrivate(window), false, 'isPrivate for a window is false when it should be');
assert.equal(isPrivate(window.tabs[0]), false, 'isPrivate for a tab is false when it should be');
window.close(done);
}
})
}
exports.testIsPrivateOnWindowOffExplicit = function(assert, done) {
windows.open({
isPrivate: false,
onOpen: function(window) {
assert.equal(isPrivate(window), false, 'isPrivate for a window is false when it should be');
assert.equal(isPrivate(window.tabs[0]), false, 'isPrivate for a tab is false when it should be');
window.close(done);
}
})
}
}
merge(module.exports, merge(module.exports,
require('./test-windows'),
require('./test-tabs'), require('./test-tabs'),
require('./test-page-mod'), require('./test-page-mod'),
require('./test-selection'), require('./test-selection'),
require('./test-panel') require('./test-panel'),
require('./test-private-browsing'),
isGlobalPBSupported ? require('./test-global-private-browsing') : {}
); );
// Doesn't make sense to test window-utils and windows on fennec,
// as there is only one window which is never private
if (!app.is("Fennec"))
merge(module.exports, require('./test-windows'));
require('sdk/test/runner').runTestsFromModule(module); require('sdk/test/runner').runTestsFromModule(module);

View File

@ -0,0 +1,150 @@
/* 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 windowUtils = require('sdk/deprecated/window-utils');
const { isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils');
const { getFrames, getWindowTitle, onFocus, isWindowPrivate, windows, isBrowser } = require('sdk/window/utils');
const { open, close, focus } = require('sdk/window/helpers');
const { isPrivate } = require('sdk/private-browsing');
const { Panel } = require('sdk/panel');
const { Widget } = require('sdk/widget');
const { fromIterator: toArray } = require('sdk/util/array');
let { Loader } = require('sdk/test/loader');
let loader = Loader(module, {
console: Object.create(console, {
error: {
value: function(e) !/DEPRECATED:/.test(e) ? console.error(e) : undefined
}
})
});
const pb = loader.require('sdk/private-browsing');
function makeEmptyBrowserWindow(options) {
options = options || {};
return open('chrome://browser/content/browser.xul', {
features: {
chrome: true,
private: !!options.private,
toolbar: true
}
});
}
exports.testShowPanelAndWidgetOnPrivateWindow = function(assert, done) {
var myPrivateWindow;
var finished = false;
var privateWindow;
var privateWindowClosed = false;
pb.once('start', function() {
assert.pass('private browsing mode started');
// make a new private window
makeEmptyBrowserWindow().then(function(window) {
myPrivateWindow = window;
let wt = windowUtils.WindowTracker({
onTrack: function(window) {
if (!isBrowser(window) || window !== myPrivateWindow) return;
assert.ok(isWindowPrivate(window), 'window is private onTrack!');
let panel = Panel({
onShow: function() {
assert.ok(this.isShowing, 'the panel is showing on the private window');
let count = 0;
let widget = Widget({
id: "testShowPanelAndWidgetOnPrivateWindow-id",
label: "My Hello Widget",
content: "Hello!",
onAttach: function(mod) {
count++;
if (count == 2) {
panel.destroy();
widget.destroy();
close(window);
}
}
});
}
}).show(window.gBrowser);
},
onUntrack: function(window) {
if (window === myPrivateWindow) {
wt.unload();
pb.once('stop', function() {
assert.pass('private browsing mode end');
done();
});
pb.deactivate();
}
}
});
assert.equal(isWindowPrivate(window), true, 'the opened window is private');
assert.equal(isPrivate(window), true, 'the opened window is private');
assert.ok(getFrames(window).length > 1, 'there are frames for private window');
assert.equal(getWindowTitle(window), window.document.title,
'getWindowTitle works');
});
});
pb.activate();
};
exports.testWindowTrackerDoesNotIgnorePrivateWindows = function(assert, done) {
var myPrivateWindow;
var count = 0;
let wt = windowUtils.WindowTracker({
onTrack: function(window) {
if (!isBrowser(window) || !isWindowPrivate(window)) return;
assert.ok(isWindowPrivate(window), 'window is private onTrack!');
if (++count == 1)
close(window);
},
onUntrack: function(window) {
if (count == 1 && isWindowPrivate(window)) {
wt.unload();
pb.once('stop', function() {
assert.pass('private browsing mode end');
done();
});
pb.deactivate();
}
}
});
pb.once('start', function() {
assert.pass('private browsing mode started');
makeEmptyBrowserWindow();
});
pb.activate();
}
exports.testWindowIteratorDoesNotIgnorePrivateWindows = function(assert, done) {
pb.once('start', function() {
// make a new private window
makeEmptyBrowserWindow().then(function(window) {
assert.ok(isWindowPrivate(window), "window is private");
assert.equal(isPrivate(window), true, 'the opened window is private');
assert.ok(toArray(windowUtils.windowIterator()).indexOf(window) > -1,
"window is in windowIterator()");
assert.ok(windows(null, { includePrivate: true }).indexOf(window) > -1,
"window is in windows()");
close(window).then(function() {
pb.once('stop', function() {
done();
});
pb.deactivate();
});
});
});
pb.activate();
};

View File

@ -0,0 +1,164 @@
/* 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 { Ci } = require('chrome');
const { isPrivateBrowsingSupported } = require('sdk/self');
const tabs = require('sdk/tabs');
const { browserWindows: windows } = require('sdk/windows');
const { isPrivate } = require('sdk/private-browsing');
const { getOwnerWindow } = require('sdk/private-browsing/window/utils');
const { is } = require('sdk/system/xul-app');
const { isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
const TAB_URL = 'data:text/html;charset=utf-8,TEST-TAB';
exports.testIsPrivateBrowsingTrue = function(assert) {
assert.ok(isPrivateBrowsingSupported,
'isPrivateBrowsingSupported property is true');
};
// test tab.open with isPrivate: true
// test isPrivate on a tab
// test getOwnerWindow on windows and tabs
exports.testGetOwnerWindow = function(assert, done) {
let window = windows.activeWindow;
let chromeWindow = getOwnerWindow(window);
assert.ok(chromeWindow instanceof Ci.nsIDOMWindow, 'associated window is found');
tabs.open({
url: 'about:blank',
isPrivate: true,
onOpen: function(tab) {
// test that getOwnerWindow works as expected
if (is('Fennec')) {
assert.notStrictEqual(chromeWindow, getOwnerWindow(tab));
assert.ok(getOwnerWindow(tab) instanceof Ci.nsIDOMWindow);
}
else {
if (isWindowPBSupported) {
assert.notStrictEqual(chromeWindow,
getOwnerWindow(tab),
'associated window is not the same for window and window\'s tab');
}
else {
assert.strictEqual(chromeWindow,
getOwnerWindow(tab),
'associated window is the same for window and window\'s tab');
}
}
let pbSupported = isTabPBSupported || isWindowPBSupported;
// test that the tab is private if it should be
assert.equal(isPrivate(tab), pbSupported);
assert.equal(isPrivate(getOwnerWindow(tab)), pbSupported);
tab.close(function() done());
}
});
};
// test that it is possible to open a private tab
exports.testTabOpenPrivate = function(assert, done) {
tabs.open({
url: TAB_URL,
isPrivate: true,
onReady: function(tab) {
assert.equal(tab.url, TAB_URL, 'opened correct tab');
assert.equal(isPrivate(tab), (isWindowPBSupported || isTabPBSupported));
tab.close(function() {
done();
});
}
});
}
// test that it is possible to open a non private tab
exports.testTabOpenPrivateDefault = function(assert, done) {
tabs.open({
url: TAB_URL,
onReady: function(tab) {
assert.equal(tab.url, TAB_URL, 'opened correct tab');
assert.equal(isPrivate(tab), false);
tab.close(function() {
done();
});
}
});
}
// test that it is possible to open a non private tab in explicit case
exports.testTabOpenPrivateOffExplicit = function(assert, done) {
tabs.open({
url: TAB_URL,
isPrivate: false,
onReady: function(tab) {
assert.equal(tab.url, TAB_URL, 'opened correct tab');
assert.equal(isPrivate(tab), false);
tab.close(function() {
done();
});
}
});
}
// test windows.open with isPrivate: true
// test isPrivate on a window
if (!is('Fennec')) {
// test that it is possible to open a private window
exports.testWindowOpenPrivate = function(assert, done) {
windows.open({
url: TAB_URL,
isPrivate: true,
onOpen: function(window) {
let tab = window.tabs[0];
tab.once('ready', function() {
assert.equal(tab.url, TAB_URL, 'opened correct tab');
assert.equal(isPrivate(tab), isWindowPBSupported, 'tab is private');
window.close(function() {
done();
});
});
}
});
};
exports.testIsPrivateOnWindowOn = function(assert, done) {
windows.open({
isPrivate: true,
onOpen: function(window) {
assert.equal(isPrivate(window), isWindowPBSupported, 'isPrivate for a window is true when it should be');
assert.equal(isPrivate(window.tabs[0]), isWindowPBSupported, 'isPrivate for a tab is false when it should be');
window.close(done);
}
});
};
exports.testIsPrivateOnWindowOffImplicit = function(assert, done) {
windows.open({
onOpen: function(window) {
assert.equal(isPrivate(window), false, 'isPrivate for a window is false when it should be');
assert.equal(isPrivate(window.tabs[0]), false, 'isPrivate for a tab is false when it should be');
window.close(done);
}
})
}
exports.testIsPrivateOnWindowOffExplicit = function(assert, done) {
windows.open({
isPrivate: false,
onOpen: function(window) {
assert.equal(isPrivate(window), false, 'isPrivate for a window is false when it should be');
assert.equal(isPrivate(window.tabs[0]), false, 'isPrivate for a tab is false when it should be');
window.close(done);
}
})
}
}

View File

@ -12,13 +12,13 @@ exports["test local vs sdk module"] = function (assert) {
} }
exports["test 3rd party vs sdk module"] = function (assert) { exports["test 3rd party vs sdk module"] = function (assert) {
// We are testing with a 3rd party package called `panel` with 3 modules // We are testing with a 3rd party package called `tabs` with 3 modules
// main, page-mod and third-party // main, page-mod and third-party
// the only way to require 3rd party package modules are to use absolute paths // the only way to require 3rd party package modules are to use absolute paths
// require("panel/main"), require("panel/page-mod"), // require("tabs/main"), require("tabs/page-mod"),
// require("panel/third-party") and also require("panel") which will refer // require("tabs/third-party") and also require("tabs") which will refer
// to panel's main package module. // to tabs's main package module.
// So require(page-mod) shouldn't map the 3rd party // So require(page-mod) shouldn't map the 3rd party
assert.equal(require("page-mod"), assert.equal(require("page-mod"),
@ -27,27 +27,27 @@ exports["test 3rd party vs sdk module"] = function (assert) {
assert.ok(require("page-mod").PageMod, assert.ok(require("page-mod").PageMod,
"page-mod module is really the sdk one"); "page-mod module is really the sdk one");
assert.equal(require("panel/page-mod").id, "page-mod", assert.equal(require("tabs/page-mod").id, "page-mod",
"panel/page-mod is the 3rd party"); "tabs/page-mod is the 3rd party");
// But require(panel) will map to 3rd party main module // But require(tabs) will map to 3rd party main module
// *and* overload the sdk module // *and* overload the sdk module
// and also any local module with the same name // and also any local module with the same name
assert.equal(require("panel").id, "panel-main", assert.equal(require("tabs").id, "tabs-main",
"Third party main module overload sdk modules"); "Third party main module overload sdk modules");
assert.equal(require("panel"), assert.equal(require("tabs"),
require("panel/main"), require("tabs/main"),
"require(panel) maps to require(panel/main)"); "require(tabs) maps to require(tabs/main)");
// So that you have to use relative path to ensure getting the local module // So that you have to use relative path to ensure getting the local module
assert.equal(require("./panel").id, assert.equal(require("./tabs").id,
"local-panel", "local-tabs",
"require(./panel) maps to the local module"); "require(./tabs) maps to the local module");
// It should still be possible to require sdk module with absolute path // It should still be possible to require sdk module with absolute path
assert.ok(require("sdk/panel").Panel, assert.ok(require("sdk/tabs").open,
"We can bypass this overloading with absolute path to sdk modules"); "We can bypass this overloading with absolute path to sdk modules");
assert.equal(require("sdk/panel"), assert.equal(require("sdk/tabs"),
require("addon-kit/panel"), require("addon-kit/tabs"),
"Old and new layout both work"); "Old and new layout both work");
} }
@ -70,8 +70,8 @@ exports.testMultipleRequirePerLine = function (assert) {
} }
exports.testSDKRequire = function (assert) { exports.testSDKRequire = function (assert) {
assert.deepEqual(Object.keys(require('sdk/widget')), ['Widget']); assert.deepEqual(Object.keys(require('sdk/page-worker')), ['Page']);
assert.equal(require('widget'), require('sdk/widget')); assert.equal(require('page-worker'), require('sdk/page-worker'));
} }
require("sdk/test/runner").runTestsFromModule(module); require("sdk/test/runner").runTestsFromModule(module);

View File

@ -0,0 +1,5 @@
/* 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/. */
exports.id = "tabs-main";

View File

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

View File

@ -0,0 +1,5 @@
/* 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/. */
exports.id = "page-mod";

View File

@ -0,0 +1,5 @@
/* 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/. */
exports.id = "local-tabs";

View File

@ -6,6 +6,7 @@
const timer = require("sdk/timers"); const timer = require("sdk/timers");
const { LoaderWithHookedConsole, deactivate, pb, pbUtils } = require("./helper"); const { LoaderWithHookedConsole, deactivate, pb, pbUtils } = require("./helper");
const tabs = require("sdk/tabs"); const tabs = require("sdk/tabs");
const { getMostRecentBrowserWindow, isWindowPrivate } = require('sdk/window/utils');
exports["test activate private mode via handler"] = function(test) { exports["test activate private mode via handler"] = function(test) {
test.waitUntilDone(); test.waitUntilDone();
@ -232,3 +233,18 @@ exports.testUnloadWhileActive = function(test) {
pb.activate(); pb.activate();
}; };
exports.testIgnoreWindow = function(test) {
test.waitUntilDone();
let window = getMostRecentBrowserWindow();
pb.once('start', function() {
test.assert(isWindowPrivate(window), 'window is private');
test.assert(!pbUtils.ignoreWindow(window), 'window is not ignored');
pb.once('stop', function() {
test.done();
});
pb.deactivate();
});
pb.activate();
};

View File

@ -9,6 +9,11 @@ const { loader } = LoaderWithHookedConsole(module);
const pb = loader.require('sdk/private-browsing'); const pb = loader.require('sdk/private-browsing');
const pbUtils = loader.require('sdk/private-browsing/utils'); const pbUtils = loader.require('sdk/private-browsing/utils');
const xulApp = require("sdk/system/xul-app");
const { openDialog, getMostRecentBrowserWindow } = require('sdk/window/utils');
const { openTab, getTabContentWindow, getActiveTab, setTabURL, closeTab } = require('sdk/tabs/utils');
const promise = require("sdk/core/promise");
const windowHelpers = require('sdk/window/helpers');
function LoaderWithHookedConsole(module) { function LoaderWithHookedConsole(module) {
let globals = {}; let globals = {};
@ -45,3 +50,40 @@ exports.deactivate = deactivate;
exports.pb = pb; exports.pb = pb;
exports.pbUtils = pbUtils; exports.pbUtils = pbUtils;
exports.LoaderWithHookedConsole = LoaderWithHookedConsole; exports.LoaderWithHookedConsole = LoaderWithHookedConsole;
exports.openWebpage = function openWebpage(url, enablePrivate) {
if (xulApp.is("Fennec")) {
let chromeWindow = getMostRecentBrowserWindow();
let rawTab = openTab(chromeWindow, url, {
isPrivate: enablePrivate
});
return {
ready: promise.resolve(getTabContentWindow(rawTab)),
close: function () {
closeTab(rawTab);
// Returns a resolved promise as there is no need to wait
return promise.resolve();
}
};
}
else {
let win = openDialog({
private: enablePrivate
});
let deferred = promise.defer();
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad, false);
let rawTab = getActiveTab(win);
setTabURL(rawTab, url);
deferred.resolve(getTabContentWindow(rawTab));
});
return {
ready: deferred.promise,
close: function () {
return windowHelpers.close(win);
}
};
}
return null;
}

View File

@ -2,14 +2,17 @@
const { Ci } = require('chrome'); const { Ci } = require('chrome');
const { openTab, closeTab } = require('sdk/tabs/utils'); const { openTab, closeTab } = require('sdk/tabs/utils');
const browserWindows = require('sdk/windows'); const { browserWindows } = require('sdk/windows');
const { getOwnerWindow } = require('sdk/private-browsing/window/utils'); const { getOwnerWindow } = require('sdk/private-browsing/window/utils');
const { isPrivate } = require('sdk/private-browsing');
exports.testIsPrivateOnTab = function(test) { exports.testIsPrivateOnTab = function(test) {
let window = browserWindows.activeWindow; let window = browserWindows.activeWindow;
let chromeWindow = getOwnerWindow(window); let chromeWindow = getOwnerWindow(window);
test.assert(chromeWindow instanceof Ci.nsIDOMWindow, 'associated window is found'); test.assert(chromeWindow instanceof Ci.nsIDOMWindow, 'associated window is found');
test.assert(!pb.isPrivate(chromeWindow), 'the top level window is not private'); test.assert(!isPrivate(chromeWindow), 'the top level window is not private');
let rawTab = openTab(chromeWindow, 'data:text/html,<h1>Hi!</h1>', { let rawTab = openTab(chromeWindow, 'data:text/html,<h1>Hi!</h1>', {
isPrivate: true isPrivate: true
@ -17,8 +20,8 @@ exports.testIsPrivateOnTab = function(test) {
// test that the tab is private // test that the tab is private
test.assert(rawTab.browser.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing); test.assert(rawTab.browser.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing);
test.assert(pb.isPrivate(rawTab.browser.contentWindow)); test.assert(isPrivate(rawTab.browser.contentWindow));
test.assert(pb.isPrivate(rawTab.browser)); test.assert(isPrivate(rawTab.browser));
closeTab(rawTab); closeTab(rawTab);
} }

View File

@ -175,6 +175,7 @@ exports["test:emit hack message"] = WorkerTest(
exports["test:n-arguments emit"] = WorkerTest( exports["test:n-arguments emit"] = WorkerTest(
DEFAULT_CONTENT_URL, DEFAULT_CONTENT_URL,
function(assert, browser, done) { function(assert, browser, done) {
let repeat = 0;
let worker = Worker({ let worker = Worker({
window: browser.contentWindow, window: browser.contentWindow,
contentScript: "new " + function WorkerScope() { contentScript: "new " + function WorkerScope() {
@ -187,10 +188,14 @@ exports["test:n-arguments emit"] = WorkerTest(
// Validate worker.port // Validate worker.port
worker.port.on("content-to-addon", function (arg1, arg2, arg3) { worker.port.on("content-to-addon", function (arg1, arg2, arg3) {
assert.equal(arg1, "first argument"); if (!repeat++) {
assert.equal(arg2, "second"); this.emit("addon-to-content", "first argument", "second", "third");
assert.equal(arg3, "third"); } else {
done(); assert.equal(arg1, "first argument");
assert.equal(arg2, "second");
assert.equal(arg3, "third");
done();
}
}); });
worker.port.emit("addon-to-content", "first argument", "second", "third"); worker.port.emit("addon-to-content", "first argument", "second", "third");
} }

View File

@ -157,10 +157,10 @@ exports['test error handling'] = function(assert) {
emit(target, 'message'); emit(target, 'message');
}; };
exports['test unhandled errors'] = function(assert) { exports['test unhandled exceptions'] = function(assert) {
let exceptions = []; let exceptions = [];
let { loader, messages } = LoaderWithHookedConsole(module); let { loader, messages } = LoaderWithHookedConsole(module);
let { emit, on } = loader.require('sdk/event/core'); let { emit, on } = loader.require('sdk/event/core');
let target = {}; let target = {};
let boom = Error('Boom!'); let boom = Error('Boom!');
@ -182,6 +182,22 @@ exports['test unhandled errors'] = function(assert) {
'error in error handler is logged'); 'error in error handler is logged');
}; };
exports['test unhandled errors'] = function(assert) {
let exceptions = [];
let { loader, messages } = LoaderWithHookedConsole(module);
let { emit, on } = loader.require('sdk/event/core');
let target = {};
let boom = Error('Boom!');
emit(target, 'error', boom);
assert.equal(messages.length, 1, 'Error was logged');
assert.equal(messages[0].type, 'exception', 'The console message is exception');
assert.ok(~String(messages[0].msg).indexOf('Boom!'),
'unhandled exception is logged');
};
exports['test count'] = function(assert) { exports['test count'] = function(assert) {
let target = {}; let target = {};

View File

@ -4,6 +4,7 @@
"use strict"; "use strict";
const { Loader } = require("sdk/test/loader");
const hiddenFrames = require("sdk/frame/hidden-frame"); const hiddenFrames = require("sdk/frame/hidden-frame");
const { HiddenFrame } = hiddenFrames; const { HiddenFrame } = hiddenFrames;
@ -29,4 +30,46 @@ exports["test Frame"] = function(assert, done) {
})); }));
}; };
exports["test frame removed properly"] = function(assert, done) {
let url = "data:text/html;charset=utf-8,<!DOCTYPE%20html>";
let hiddenFrame = hiddenFrames.add(HiddenFrame({
onReady: function () {
let frame = this.element;
assert.ok(frame.parentNode, "frame has a parent node");
hiddenFrames.remove(hiddenFrame);
assert.ok(!frame.parentNode, "frame no longer has a parent node");
done();
}
}));
};
exports["test unload detaches panels"] = function(assert, done) {
let loader = Loader(module);
let { add, remove, HiddenFrame } = loader.require("sdk/frame/hidden-frame");
let frames = []
function ready() {
frames.push(this.element);
if (frames.length === 2) complete();
}
add(HiddenFrame({ onReady: ready }));
add(HiddenFrame({ onReady: ready }));
function complete() {
frames.forEach(function(frame) {
assert.ok(frame.parentNode, "frame is in the document");
})
loader.unload();
frames.forEach(function(frame) {
assert.ok(!frame.parentNode, "frame isn't in the document'");
});
done();
}
};
require("test").run(exports); require("test").run(exports);

View File

@ -9,15 +9,16 @@ const { Loader } = require('sdk/test/loader');
const tabs = require("sdk/tabs"); const tabs = require("sdk/tabs");
const timer = require("sdk/timers"); const timer = require("sdk/timers");
const { Cc, Ci } = require("chrome"); const { Cc, Ci } = require("chrome");
const { open, openDialog, getFrames, getMostRecentBrowserWindow } = require('sdk/window/utils'); const { open, getFrames, getMostRecentBrowserWindow } = require('sdk/window/utils');
const windowUtils = require('sdk/deprecated/window-utils'); const windowUtils = require('sdk/deprecated/window-utils');
const windowHelpers = require('sdk/window/helpers');
const { getTabContentWindow, getActiveTab, setTabURL, openTab, closeTab } = require('sdk/tabs/utils'); const { getTabContentWindow, getActiveTab, setTabURL, openTab, closeTab } = require('sdk/tabs/utils');
const xulApp = require("sdk/system/xul-app"); const xulApp = require("sdk/system/xul-app");
const { data } = require('sdk/self'); const { data, isPrivateBrowsingSupported } = require('sdk/self');
const { isPrivate } = require('sdk/private-browsing'); const { isPrivate } = require('sdk/private-browsing');
const { isTabPBSupported, isWindowPBSupported } = require('sdk/private-browsing/utils'); const { openWebpage } = require('./private-browsing/helper');
const { isTabPBSupported, isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils');
const promise = require("sdk/core/promise"); const promise = require("sdk/core/promise");
const { pb } = require('./private-browsing/helper');
/* XXX This can be used to delay closing the test Firefox instance for interactive /* XXX This can be used to delay closing the test Firefox instance for interactive
* testing or visual inspection. This test is registered first so that it runs * testing or visual inspection. This test is registered first so that it runs
@ -1055,36 +1056,6 @@ exports.testEvents = function(test) {
); );
}; };
function openWebpage(url, enablePrivate) {
if (xulApp.is("Fennec")) {
let chromeWindow = getMostRecentBrowserWindow();
let rawTab = openTab(chromeWindow, url, {
isPrivate: enablePrivate
});
return {
close: function () {
closeTab(rawTab)
// Returns a resolved promise as there is no need to wait
return promise.resolve();
}
};
}
else {
let win = openDialog({
private: enablePrivate
});
win.addEventListener("load", function onLoad() {
win.removeEventListener("load", onLoad, false);
setTabURL(getActiveTab(win), url);
});
return {
close: function () {
return windowHelpers.close(win);
}
};
}
}
exports["test page-mod on private tab"] = function (test) { exports["test page-mod on private tab"] = function (test) {
test.waitUntilDone(); test.waitUntilDone();
let privateUri = "data:text/html;charset=utf-8," + let privateUri = "data:text/html;charset=utf-8," +
@ -1113,3 +1084,46 @@ exports["test page-mod on private tab"] = function (test) {
let page1 = openWebpage(privateUri, true); let page1 = openWebpage(privateUri, true);
let page2 = openWebpage(nonPrivateUri, false); let page2 = openWebpage(nonPrivateUri, false);
} }
exports["test page-mod on private tab in global pb"] = function (test) {
test.waitUntilDone();
if (!isGlobalPBSupported) {
test.pass();
return test.done();
}
let privateUri = "data:text/html;charset=utf-8," +
"<iframe%20src=\"data:text/html;charset=utf-8,frame\"/>";
let pageMod = new PageMod({
include: privateUri,
onAttach: function(worker) {
test.assertEqual(worker.tab.url,
privateUri,
"page-mod should attach");
test.assertEqual(isPrivateBrowsingSupported,
false,
"private browsing is not supported");
test.assert(isPrivate(worker),
"The worker is really non-private");
test.assert(isPrivate(worker.tab),
"The document is really non-private");
pageMod.destroy();
worker.tab.close(function() {
pb.once('stop', function() {
test.pass('global pb stop');
test.done();
});
pb.deactivate();
});
}
});
let page1;
pb.once('start', function() {
test.pass('global pb start');
tabs.open({ url: privateUri });
});
pb.activate();
}

View File

@ -325,6 +325,20 @@ exports.testContentScriptOptionsOption = function(assert, done) {
}); });
}; };
exports.testMessageQueue = function (assert, done) {
let page = new Page({
contentScript: 'self.on("message", function (m) {' +
'self.postMessage(m);' +
'});',
contentURL: 'data:text/html;charset=utf-8,',
});
page.postMessage('ping');
page.on('message', function (m) {
assert.equal(m, 'ping', 'postMessage should queue messages');
done();
});
};
function isDestroyed(page) { function isDestroyed(page) {
try { try {
page.postMessage("foo"); page.postMessage("foo");

View File

@ -10,9 +10,11 @@ const timer = require("sdk/timers");
const self = require('sdk/self'); const self = require('sdk/self');
const { open, close, focus } = require('sdk/window/helpers'); const { open, close, focus } = require('sdk/window/helpers');
const { isPrivate } = require('sdk/private-browsing'); const { isPrivate } = require('sdk/private-browsing');
const { isWindowPBSupported } = require('sdk/private-browsing/utils'); const { isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils');
const { defer } = require('sdk/core/promise'); const { defer } = require('sdk/core/promise');
const { getMostRecentBrowserWindow } = require('sdk/window/utils'); const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { getWindow } = require('sdk/panel/window');
const { pb } = require('./private-browsing/helper');
const SVG_URL = self.data.url('mofo_logo.SVG'); const SVG_URL = self.data.url('mofo_logo.SVG');
@ -287,7 +289,10 @@ exports["test Anchor And Arrow"] = function(assert, done) {
const { Panel } = require('sdk/panel'); const { Panel } = require('sdk/panel');
let count = 0; let count = 0;
function newPanel(tab, anchor) { let queue = [];
let tab;
function newPanel(anchor) {
let panel = Panel({ let panel = Panel({
contentURL: "data:text/html;charset=utf-8,<html><body style='padding: 0; margin: 0; " + contentURL: "data:text/html;charset=utf-8,<html><body style='padding: 0; margin: 0; " +
"background: gray; text-align: center;'>Anchor: " + "background: gray; text-align: center;'>Anchor: " +
@ -295,16 +300,22 @@ exports["test Anchor And Arrow"] = function(assert, done) {
width: 200, width: 200,
height: 100, height: 100,
onShow: function () { onShow: function () {
count++;
panel.destroy(); panel.destroy();
if (count==5) { next();
assert.pass("All anchored panel test displayed");
tab.close(function () {
done();
});
}
} }
}); });
queue.push({ panel: panel, anchor: anchor });
}
function next () {
if (!queue.length) {
assert.pass("All anchored panel test displayed");
tab.close(function () {
done();
});
return;
}
let { panel, anchor } = queue.shift();
panel.show(anchor); panel.show(anchor);
} }
@ -321,22 +332,105 @@ exports["test Anchor And Arrow"] = function(assert, done) {
tabs.open({ tabs.open({
url: url, url: url,
onReady: function(tab) { onReady: function(_tab) {
tab = _tab;
let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator). getService(Ci.nsIWindowMediator).
getMostRecentWindow("navigator:browser"); getMostRecentWindow("navigator:browser");
let window = browserWindow.content; let window = browserWindow.content;
newPanel(tab, window.document.getElementById('tl')); newPanel(window.document.getElementById('tl'));
newPanel(tab, window.document.getElementById('tr')); newPanel(window.document.getElementById('tr'));
newPanel(tab, window.document.getElementById('bl')); newPanel(window.document.getElementById('bl'));
newPanel(tab, window.document.getElementById('br')); newPanel(window.document.getElementById('br'));
let anchor = browserWindow.document.getElementById("identity-box"); let anchor = browserWindow.document.getElementById("identity-box");
newPanel(tab, anchor); newPanel(anchor);
next();
} }
}); });
};
exports["test Panel Focus True"] = function(assert, done) {
const { Panel } = require('sdk/panel');
const FM = Cc["@mozilla.org/focus-manager;1"].
getService(Ci.nsIFocusManager);
let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator).
getMostRecentWindow("navigator:browser");
// Make sure there is a focused element
browserWindow.document.documentElement.focus();
// Get the current focused element
let focusedElement = FM.focusedElement;
let panel = Panel({
contentURL: "about:buildconfig",
focus: true,
onShow: function () {
assert.ok(focusedElement !== FM.focusedElement,
"The panel takes the focus away.");
done();
}
});
panel.show();
};
exports["test Panel Focus False"] = function(assert, done) {
const { Panel } = require('sdk/panel');
const FM = Cc["@mozilla.org/focus-manager;1"].
getService(Ci.nsIFocusManager);
let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator).
getMostRecentWindow("navigator:browser");
// Make sure there is a focused element
browserWindow.document.documentElement.focus();
// Get the current focused element
let focusedElement = FM.focusedElement;
let panel = Panel({
contentURL: "about:buildconfig",
focus: false,
onShow: function () {
assert.ok(focusedElement === FM.focusedElement,
"The panel does not take the focus away.");
done();
}
});
panel.show();
};
exports["test Panel Focus Not Set"] = function(assert, done) {
const { Panel } = require('sdk/panel');
const FM = Cc["@mozilla.org/focus-manager;1"].
getService(Ci.nsIFocusManager);
let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
getService(Ci.nsIWindowMediator).
getMostRecentWindow("navigator:browser");
// Make sure there is a focused element
browserWindow.document.documentElement.focus();
// Get the current focused element
let focusedElement = FM.focusedElement;
let panel = Panel({
contentURL: "about:buildconfig",
onShow: function () {
assert.ok(focusedElement !== FM.focusedElement,
"The panel takes the focus away.");
done();
}
});
panel.show();
}; };
exports["test Panel Text Color"] = function(assert, done) { exports["test Panel Text Color"] = function(assert, done) {
@ -518,7 +612,7 @@ exports["test console.log in Panel"] = function(assert, done) {
}); });
panel.show(); panel.show();
function onMessage(type, message) { function onMessage(type, message) {
assert.equal(type, 'log', 'console.log() works'); assert.equal(type, 'log', 'console.log() works');
assert.equal(message, text, 'console.log() works'); assert.equal(message, text, 'console.log() works');
@ -658,6 +752,106 @@ function testShowPanel(assert, panel) {
return promise; return promise;
} }
exports['test Style Applied Only Once'] = function (assert, done) {
let loader = Loader(module);
let panel = loader.require("sdk/panel").Panel({
contentURL: "data:text/html;charset=utf-8,",
contentScript:
'self.port.on("check",function() { self.port.emit("count", document.getElementsByTagName("style").length); });' +
'self.port.on("ping", function (count) { self.port.emit("pong", count); });'
});
panel.port.on('count', function (styleCount) {
assert.equal(styleCount, 1, 'should only have one style');
done();
});
panel.port.on('pong', function (counter) {
panel[--counter % 2 ? 'hide' : 'show']();
panel.port.emit(!counter ? 'check' : 'ping', counter);
});
panel.on('show', init);
panel.show();
function init () {
panel.removeListener('show', init);
panel.port.emit('ping', 10);
}
};
exports['test Only One Panel Open Concurrently'] = function (assert, done) {
const loader = Loader(module);
const { Panel } = loader.require('sdk/panel')
let panelA = Panel({
contentURL: 'about:buildconfig'
});
let panelB = Panel({
contentURL: 'about:buildconfig',
onShow: function () {
// When loading two panels simulataneously, only the second
// should be shown, never showing the first
assert.equal(panelA.isShowing, false, 'First panel is hidden');
assert.equal(panelB.isShowing, true, 'Second panel is showing');
panelC.show();
}
});
let panelC = Panel({
contentURL: 'about:buildconfig',
onShow: function () {
assert.equal(panelA.isShowing, false, 'First panel is hidden');
assert.equal(panelB.isShowing, false, 'Second panel is hidden');
assert.equal(panelC.isShowing, true, 'Third panel is showing');
done();
}
});
panelA.show();
panelB.show();
};
if (isWindowPBSupported) {
exports.testGetWindow = function(assert, done) {
let activeWindow = getMostRecentBrowserWindow();
open(null, { features: {
toolbar: true,
chrome: true,
private: true
} }).then(function(window) {
assert.ok(isPrivate(window), 'window is private');
assert.equal(getWindow(window.gBrowser), null, 'private window elements returns null');
assert.equal(getWindow(activeWindow.gBrowser), activeWindow, 'non-private window elements returns window');
close(window).then(done);
})
}
}
else if (isGlobalPBSupported) {
exports.testGetWindow = function(assert, done) {
let activeWindow = getMostRecentBrowserWindow();
assert.equal(getWindow(activeWindow.gBrowser), activeWindow, 'non-private window elements returns window');
pb.once('start', function() {
assert.ok(isPrivate(activeWindow), 'window is private');
assert.equal(getWindow(activeWindow.gBrowser), activeWindow, 'private window elements returns window');
open(null, { features: {
toolbar: true,
chrome: true
} }).then(function(window) {
assert.ok(isPrivate(window), 'window is private');
assert.equal(getWindow(window.gBrowser), window, 'private window elements returns window');
assert.equal(getWindow(activeWindow.gBrowser), activeWindow, 'active window elements returns window');
pb.once('stop', done);
pb.deactivate();
})
});
pb.activate();
}
}
try { try {
require("sdk/panel"); require("sdk/panel");
} }

View File

@ -0,0 +1,70 @@
'use strict';
const { getTabs } = require('sdk/tabs/utils');
const { isGlobalPBSupported, isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
const { browserWindows } = require('sdk/windows');
const tabs = require('sdk/tabs');
const { pb } = require('./private-browsing/helper');
const { isPrivate } = require('sdk/private-browsing');
const { openTab } = require('sdk/tabs/utils');
const { open, close } = require('sdk/window/helpers');
const { windows } = require('sdk/window/utils');
const { getMostRecentBrowserWindow } = require('sdk/window/utils');
const { fromIterator } = require('sdk/util/array');
if (isGlobalPBSupported) {
exports.testGetTabs = function(assert, done) {
pb.once('start', function() {
tabs.open({
url: 'about:blank',
inNewWindow: true,
onOpen: function(tab) {
assert.equal(getTabs().length, 2, 'there are two tabs');
assert.equal(browserWindows.length, 2, 'there are two windows');
pb.once('stop', function() {
done();
});
pb.deactivate();
}
});
});
pb.activate();
};
}
else if (isWindowPBSupported) {
exports.testGetTabs = function(assert, done) {
open(null, {
features: {
private: true,
toolbar: true,
chrome: true
}
}).then(function(window) {
assert.ok(isPrivate(window), 'new tab is private');
assert.equal(getTabs().length, 1, 'there is one tab found');
assert.equal(browserWindows.length, 1, 'there is one window found');
fromIterator(browserWindows).forEach(function(window) {
assert.ok(!isPrivate(window), 'all found windows are not private');
});
assert.equal(windows(null, {includePrivate: true}).length, 2, 'there are really two windows');
close(window).then(done);
});
};
}
else if (isTabPBSupported) {
exports.testGetTabs = function(assert, done) {
tabs.once('open', function(tab) {
assert.ok(isPrivate(tab), 'new tab is private');
assert.equal(getTabs().length, 2, 'there are two tabs found');
assert.equal(browserWindows.length, 1, 'there is one window');
tab.close(function() {
done();
});
});
openTab(getMostRecentBrowserWindow(), 'about:blank', {
isPrivate: true
});
};
}
require('test').run(exports);

View File

@ -8,9 +8,10 @@ const { browserWindows } = require('sdk/windows');
const tabs = require('sdk/tabs'); const tabs = require('sdk/tabs');
const { isPrivate } = require('sdk/private-browsing'); const { isPrivate } = require('sdk/private-browsing');
const { openDialog } = require('sdk/window/utils'); const { openDialog } = require('sdk/window/utils');
const pbUtils = require('sdk/private-browsing/utils');
const { isWindowPrivate } = require('sdk/window/utils'); const { isWindowPrivate } = require('sdk/window/utils');
const { setTimeout } = require('sdk/timers'); const { setTimeout } = require('sdk/timers');
const { openWebpage } = require('./private-browsing/helper');
const { isTabPBSupported, isWindowPBSupported } = require('sdk/private-browsing/utils');
const URL = 'data:text/html;charset=utf-8,<html><head><title>#title#</title></head></html>'; const URL = 'data:text/html;charset=utf-8,<html><head><title>#title#</title></head></html>';
@ -323,34 +324,27 @@ exports.testTabOpenPrivate = function(test) {
// We need permission flag in order to see private window's tabs // We need permission flag in order to see private window's tabs
exports.testPrivateAreNotListed = function (test) { exports.testPrivateAreNotListed = function (test) {
test.waitUntilDone();
let originalTabCount = tabs.length; let originalTabCount = tabs.length;
let win = openDialog({ let page = openWebpage("about:blank", true);
private: true if (!page) {
}); test.pass("Private browsing isn't supported in this release");
return;
}
win.addEventListener("load", function onload() { test.waitUntilDone();
win.removeEventListener("load", onload); page.ready.then(function (win) {
if (isTabPBSupported || isWindowPBSupported) {
// PWPB case test.assert(isWindowPrivate(win), "the window is private");
if (pbUtils.isWindowPBSupported) {
test.assert(isWindowPrivate(win), "window is private");
test.assertEqual(tabs.length, originalTabCount, test.assertEqual(tabs.length, originalTabCount,
'New private window\'s tab isn\'t visible in tabs list'); 'but the tab is *not* visible in tabs list');
} }
else { else {
// Global case, openDialog didn't opened a private window/tab test.assert(!isWindowPrivate(win), "the window isn't private");
test.assert(!isWindowPrivate(win), "window is private");
test.assertEqual(tabs.length, originalTabCount + 1, test.assertEqual(tabs.length, originalTabCount + 1,
'New non-private window\'s tab is visible in tabs list'); 'so that the tab is visible is tabs list');
} }
page.close().then(test.done.bind(test));
win.addEventListener("unload", function onunload() {
win.removeEventListener('unload', onunload);
test.done();
});
win.close();
}); });
} }

View File

@ -0,0 +1,152 @@
/* 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 windowUtils = require('sdk/deprecated/window-utils');
const { isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils');
const { getFrames, getWindowTitle, onFocus, isWindowPrivate, windows, isBrowser } = require('sdk/window/utils');
const { open, close, focus } = require('sdk/window/helpers');
const { isPrivate } = require('sdk/private-browsing');
const { pb } = require('./private-browsing/helper');
const { fromIterator: toArray } = require('sdk/util/array');
function makeEmptyBrowserWindow(options) {
options = options || {};
return open('chrome://browser/content/browser.xul', {
features: {
chrome: true,
private: !!options.private,
toolbar: true
}
});
}
exports.testShowPanelAndWidgetOnPrivateWindow = function(assert, done) {
var myPrivateWindow;
var finished = false;
var privateWindow;
var privateWindowClosed = false;
var { Panel } = require('sdk/panel');
var { Widget } = require('sdk/widget');
pb.once('start', function() {
assert.pass('private browsing mode started');
// make a new private window
makeEmptyBrowserWindow().then(function(window) {
myPrivateWindow = window;
let wt = windowUtils.WindowTracker({
onTrack: function(window) {
if (!isBrowser(window) || window !== myPrivateWindow) return;
assert.ok(isWindowPrivate(window), 'window is private onTrack!');
let panel = Panel({
onShow: function() {
assert.ok(this.isShowing, 'the panel is showing on the private window');
let count = 0;
let widget = Widget({
id: "testShowPanelAndWidgetOnPrivateWindow-id",
label: "My Hello Widget",
content: "Hello!",
onAttach: function(mod) {
count++;
if (count == 2) {
panel.destroy();
widget.destroy();
close(window);
}
}
});
}
}).show(window.gBrowser);
},
onUntrack: function(window) {
if (window === myPrivateWindow) {
wt.unload();
pb.once('stop', function() {
assert.pass('private browsing mode end');
done();
});
pb.deactivate();
}
}
});
assert.equal(isWindowPrivate(window), true, 'the opened window is private');
assert.equal(isPrivate(window), true, 'the opened window is private');
assert.ok(getFrames(window).length > 1, 'there are frames for private window');
assert.equal(getWindowTitle(window), window.document.title,
'getWindowTitle works');
});
});
pb.activate();
};
exports.testWindowTrackerDoesNotIgnorePrivateWindows = function(assert, done) {
var myPrivateWindow;
var count = 0;
let wt = windowUtils.WindowTracker({
onTrack: function(window) {
if (!isBrowser(window) || !isWindowPrivate(window)) return;
assert.ok(isWindowPrivate(window), 'window is private onTrack!');
if (++count == 1)
close(window);
},
onUntrack: function(window) {
if (count == 1 && isWindowPrivate(window)) {
wt.unload();
pb.once('stop', function() {
assert.pass('private browsing mode end');
done();
});
pb.deactivate();
}
}
});
pb.once('start', function() {
assert.pass('private browsing mode started');
makeEmptyBrowserWindow();
});
pb.activate();
}
exports.testWindowIteratorDoesNotIgnorePrivateWindows = function(assert, done) {
pb.once('start', function() {
// make a new private window
makeEmptyBrowserWindow().then(function(window) {
assert.ok(isWindowPrivate(window), "window is private");
assert.equal(isPrivate(window), true, 'the opened window is private');
assert.ok(toArray(windowUtils.windowIterator()).indexOf(window) > -1,
"window is in windowIterator()");
assert.ok(windows(null, { includePrivate: true }).indexOf(window) > -1,
"window is in windows()");
close(window).then(function() {
pb.once('stop', function() {
done();
});
pb.deactivate();
});
});
});
pb.activate();
};
if (!isGlobalPBSupported) {
module.exports = {
"test Unsupported Test": function UnsupportedTest (assert) {
assert.pass(
"Skipping global private browsing tests");
}
}
}
require("test").run(exports);

View File

@ -30,14 +30,10 @@ exports.testWindowTrackerIgnoresPrivateWindows = function(assert, done) {
let wt = windowUtils.WindowTracker({ let wt = windowUtils.WindowTracker({
onTrack: function(window) { onTrack: function(window) {
if (isWindowPrivate(window)) { assert.ok(!isWindowPrivate(window), 'private window was not tracked!');
assert.fail('private window was tracked!');
}
}, },
onUntrack: function(window) { onUntrack: function(window) {
if (isWindowPrivate(window)) { assert.ok(!isWindowPrivate(window), 'private window was not tracked!');
assert.fail('private window was tracked!');
}
// PWPB case // PWPB case
if (window === myPrivateWindow && isWindowPBSupported) { if (window === myPrivateWindow && isWindowPBSupported) {
privateWindowClosed = true; privateWindowClosed = true;

View File

@ -1,4 +1,6 @@
ManifestDestiny==0.5.6
mozprocess==0.9 mozprocess==0.9
mozprofile==0.6
mozrunner==5.15 mozrunner==5.15
mozdevice==0.21 mozdevice==0.21
mozcrash==0.5 mozcrash==0.5

View File

@ -1,6 +1,6 @@
[{ [{
"size": 622597962, "size": 621007059,
"digest": "31e1e74c621b98ee0970ed656636b495c4c90e82060442e8f19d35bc1839e55030736b4aa689c8325f9b9b80aa1cc6b1981dd95b88c8c63ca8e5b0db9957ac0a", "digest": "7ab61ca7a6c25297bbe3ec8328b2a8b4298cb0656747292e86a8caeef08500f2c68c00b5be02f2d83afaa2ae2fb7240e4d8a321c786fd1b0d57aeaa6e257dad5",
"algorithm": "sha512", "algorithm": "sha512",
"filename": "emulator.zip" "filename": "emulator.zip"
}] }]

View File

@ -1,5 +1,5 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1363988948000"> <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1364316540000">
<emItems> <emItems>
<emItem blockID="i58" id="webmaster@buzzzzvideos.info"> <emItem blockID="i58" id="webmaster@buzzzzvideos.info">
<versionRange minVersion="0" maxVersion="*"> <versionRange minVersion="0" maxVersion="*">
@ -372,6 +372,10 @@
<versionRange minVersion="2.0" maxVersion="2.0"> <versionRange minVersion="2.0" maxVersion="2.0">
</versionRange> </versionRange>
</emItem> </emItem>
<emItem blockID="i326" id="/^((support2_en@adobe14\.com)|(XN4Xgjw7n4@yUWgc\.com)|(C7yFVpIP@WeolS3acxgS\.com)|(Kbeu4h0z@yNb7QAz7jrYKiiTQ3\.com)|(aWQzX@a6z4gWdPu8FF\.com)|(CBSoqAJLYpCbjTP90@JoV0VMywCjsm75Y0toAd\.com)|(zZ2jWZ1H22Jb5NdELHS@o0jQVWZkY1gx1\.com))$/">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i162" id="{EB7508CA-C7B2-46E0-8C04-3E94A035BD49}"> <emItem blockID="i162" id="{EB7508CA-C7B2-46E0-8C04-3E94A035BD49}">
<versionRange minVersion="0" maxVersion="*" severity="3"> <versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange> </versionRange>
@ -395,10 +399,6 @@
<versionRange minVersion="0" maxVersion="*"> <versionRange minVersion="0" maxVersion="*">
</versionRange> </versionRange>
</emItem> </emItem>
<emItem blockID="i326" id="/^((support2_en@adobe14\.com)|(XN4Xgjw7n4@yUWgc\.com)|(C7yFVpIP@WeolS3acxgS\.com)|(Kbeu4h0z@yNb7QAz7jrYKiiTQ3\.com)|(aWQzX@a6z4gWdPu8FF\.com)|(CBSoqAJLYpCbjTP90@JoV0VMywCjsm75Y0toAd\.com))$/">
<versionRange minVersion="0" maxVersion="*" severity="3">
</versionRange>
</emItem>
<emItem blockID="i312" id="extension21804@extension21804.com"> <emItem blockID="i312" id="extension21804@extension21804.com">
<versionRange minVersion="0" maxVersion="*" severity="1"> <versionRange minVersion="0" maxVersion="*" severity="1">
</versionRange> </versionRange>
@ -803,7 +803,28 @@
<pluginItem blockID="p316"> <pluginItem blockID="p316">
<match name="filename" exp="(NPSWF[0-9_]*\.dll)|(Flash\ Player\.plugin)" /> <versionRange minVersion="11.2.202.275" maxVersion="11.4.402.286.9999" severity="0" vulnerabilitystatus="1"> <match name="filename" exp="(NPSWF[0-9_]*\.dll)|(Flash\ Player\.plugin)" /> <versionRange minVersion="11.2.202.275" maxVersion="11.4.402.286.9999" severity="0" vulnerabilitystatus="1">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}"> <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="20.0a1" maxVersion="*" /> <versionRange minVersion="21.0a1" maxVersion="*" />
</targetApplication>
</versionRange>
</pluginItem>
<pluginItem blockID="p328">
<match name="filename" exp="Silverlight\.plugin" /> <versionRange minVersion="5.1" maxVersion="5.1.20124.9999" severity="0" vulnerabilitystatus="1">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="19.0a1" maxVersion="*" />
</targetApplication>
</versionRange>
</pluginItem>
<pluginItem blockID="p330">
<match name="description" exp="^Shockwave Flash (([1-9]\.[0-9]+)|(10\.([0-2]|(3 r(([0-9][0-9]?)|1(([0-7][0-9])|8[0-2]))))))( |$)" /> <match name="filename" exp="libflashplayer\.so" /> <versionRange severity="0" vulnerabilitystatus="1">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="19.0a1" maxVersion="*" />
</targetApplication>
</versionRange>
</pluginItem>
<pluginItem blockID="p332">
<match name="description" exp="^Shockwave Flash 11.(0|1) r[0-9]{1,3}$" /> <match name="filename" exp="libflashplayer\.so" /> <versionRange severity="0" vulnerabilitystatus="1">
<targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
<versionRange minVersion="19.0a1" maxVersion="*" />
</targetApplication> </targetApplication>
</versionRange> </versionRange>
</pluginItem> </pluginItem>

View File

@ -10,15 +10,6 @@
// From nsEventStateManager.cpp. // From nsEventStateManager.cpp.
const MOUSE_SCROLL_ZOOM = 3; const MOUSE_SCROLL_ZOOM = 3;
Cu.import('resource://gre/modules/ContentPrefInstance.jsm');
function getContentPrefs(aWindow) {
let context = aWindow ? aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsILoadContext) : null;
return new ContentPrefInstance(context);
}
/** /**
* Controls the "full zoom" setting and its site-specific preferences. * Controls the "full zoom" setting and its site-specific preferences.
*/ */
@ -26,17 +17,6 @@ var FullZoom = {
// Identifies the setting in the content prefs database. // Identifies the setting in the content prefs database.
name: "browser.content.full-zoom", name: "browser.content.full-zoom",
// The global value (if any) for the setting. Lazily loaded from the service
// when first requested, then updated by the pref change listener as it changes.
// If there is no global value, then this should be undefined.
get globalValue() {
var globalValue = getContentPrefs(gBrowser.contentDocument.defaultView).getPref(null, this.name);
if (typeof globalValue != "undefined")
globalValue = this._ensureValid(globalValue);
delete this.globalValue;
return this.globalValue = globalValue;
},
// browser.zoom.siteSpecific preference cache // browser.zoom.siteSpecific preference cache
_siteSpecificPref: undefined, _siteSpecificPref: undefined,
@ -64,7 +44,9 @@ var FullZoom = {
window.addEventListener("DOMMouseScroll", this, false); window.addEventListener("DOMMouseScroll", this, false);
// Register ourselves with the service so we know when our pref changes. // Register ourselves with the service so we know when our pref changes.
getContentPrefs().addObserver(this.name, this); this._cps2 = Cc["@mozilla.org/content-pref/service;1"].
getService(Ci.nsIContentPrefService2);
this._cps2.addObserverForName(this.name, this);
this._siteSpecificPref = this._siteSpecificPref =
gPrefService.getBoolPref("browser.zoom.siteSpecific"); gPrefService.getBoolPref("browser.zoom.siteSpecific");
@ -77,7 +59,7 @@ var FullZoom = {
destroy: function FullZoom_destroy() { destroy: function FullZoom_destroy() {
gPrefService.removeObserver("browser.zoom.", this); gPrefService.removeObserver("browser.zoom.", this);
getContentPrefs().removeObserver(this.name, this); this._cps2.removeObserverForName(this.name, this);
window.removeEventListener("DOMMouseScroll", this, false); window.removeEventListener("DOMMouseScroll", this, false);
}, },
@ -133,7 +115,14 @@ var FullZoom = {
// We have to call _applySettingToPref in a timeout because we handle // We have to call _applySettingToPref in a timeout because we handle
// the event before the event state manager has a chance to apply the zoom // the event before the event state manager has a chance to apply the zoom
// during nsEventStateManager::PostHandleEvent. // during nsEventStateManager::PostHandleEvent.
window.setTimeout(function (self) { self._applySettingToPref() }, 0, this); //
// Since the zoom will have already been updated by the time
// _applySettingToPref is called, pass true to suppress updating the zoom
// again. Note that this is not an optimization: due to asynchronicity of
// the content preference service, the zoom may have been updated again by
// the time that onContentPrefSet is called as a result of this call to
// _applySettingToPref.
window.setTimeout(function (self) self._applySettingToPref(true), 0, this);
}, },
// nsIObserver // nsIObserver
@ -158,33 +147,53 @@ var FullZoom = {
// nsIContentPrefObserver // nsIContentPrefObserver
onContentPrefSet: function FullZoom_onContentPrefSet(aGroup, aName, aValue) { onContentPrefSet: function FullZoom_onContentPrefSet(aGroup, aName, aValue) {
let contentPrefs = getContentPrefs(gBrowser.contentDocument.defaultView); if (this._ignoreNextOnContentPrefSet) {
if (aGroup == contentPrefs.grouper.group(gBrowser.currentURI)) delete this._ignoreNextOnContentPrefSet;
this._applyPrefToSetting(aValue); return;
else if (aGroup == null) {
this.globalValue = this._ensureValid(aValue);
// If the current page doesn't have a site-specific preference,
// then its zoom should be set to the new global preference now that
// the global preference has changed.
if (!contentPrefs.hasPref(gBrowser.currentURI, this.name))
this._applyPrefToSetting();
} }
this._onContentPrefChanged(aGroup, aValue);
}, },
onContentPrefRemoved: function FullZoom_onContentPrefRemoved(aGroup, aName) { onContentPrefRemoved: function FullZoom_onContentPrefRemoved(aGroup, aName) {
let contentPrefs = getContentPrefs(gBrowser.contentDocument.defaultView); this._onContentPrefChanged(aGroup, undefined);
if (aGroup == contentPrefs.grouper.group(gBrowser.currentURI)) },
this._applyPrefToSetting();
else if (aGroup == null) {
this.globalValue = undefined;
// If the current page doesn't have a site-specific preference, /**
// then its zoom should be set to the default preference now that * Appropriately updates the zoom level after a content preference has
// the global preference has changed. * changed.
if (!contentPrefs.hasPref(gBrowser.currentURI, this.name)) *
this._applyPrefToSetting(); * @param aGroup The group of the changed preference.
* @param aValue The new value of the changed preference. Pass undefined to
* indicate the preference's removal.
*/
_onContentPrefChanged: function FullZoom__onContentPrefChanged(aGroup, aValue) {
if (!gBrowser.currentURI)
return;
let domain = this._cps2.extractDomain(gBrowser.currentURI.spec);
if (aGroup) {
if (aGroup == domain)
this._applyPrefToSetting(aValue);
return;
} }
this._globalValue = aValue === undefined ? aValue :
this._ensureValid(aValue);
// If the current page doesn't have a site-specific preference, then its
// zoom should be set to the new global preference now that the global
// preference has changed.
let hasPref = false;
let ctxt = this._loadContextFromWindow(gBrowser.contentWindow);
this._cps2.getByDomainAndName(gBrowser.currentURI.spec, this.name, ctxt, {
handleResult: function () hasPref = true,
handleCompletion: function () {
if (!hasPref &&
gBrowser.currentURI &&
this._cps2.extractDomain(gBrowser.currentURI.spec) == domain)
this._applyPrefToSetting();
}.bind(this)
});
}, },
// location change observer // location change observer
@ -201,12 +210,16 @@ var FullZoom = {
* (optional) browser object displaying the document * (optional) browser object displaying the document
*/ */
onLocationChange: function FullZoom_onLocationChange(aURI, aIsTabSwitch, aBrowser) { onLocationChange: function FullZoom_onLocationChange(aURI, aIsTabSwitch, aBrowser) {
if (!aURI || (aIsTabSwitch && !this.siteSpecific)) if (!aURI || (aIsTabSwitch && !this.siteSpecific)) {
this._notifyOnLocationChange();
return; return;
}
// Avoid the cps roundtrip and apply the default/global pref. // Avoid the cps roundtrip and apply the default/global pref.
if (aURI.spec == "about:blank") { if (aURI.spec == "about:blank") {
this._applyPrefToSetting(undefined, aBrowser); this._applyPrefToSetting(undefined, aBrowser, function () {
this._notifyOnLocationChange();
}.bind(this));
return; return;
} }
@ -215,24 +228,32 @@ var FullZoom = {
// Media documents should always start at 1, and are not affected by prefs. // Media documents should always start at 1, and are not affected by prefs.
if (!aIsTabSwitch && browser.contentDocument.mozSyntheticDocument) { if (!aIsTabSwitch && browser.contentDocument.mozSyntheticDocument) {
ZoomManager.setZoomForBrowser(browser, 1); ZoomManager.setZoomForBrowser(browser, 1);
this._notifyOnLocationChange();
return; return;
} }
let contentPrefs = getContentPrefs(gBrowser.contentDocument.defaultView); let ctxt = this._loadContextFromWindow(browser.contentWindow);
if (contentPrefs.hasCachedPref(aURI, this.name)) { let pref = this._cps2.getCachedByDomainAndName(aURI.spec, this.name, ctxt);
let zoomValue = contentPrefs.getPref(aURI, this.name); if (pref) {
this._applyPrefToSetting(zoomValue, browser); this._applyPrefToSetting(pref.value, browser, function () {
} else { this._notifyOnLocationChange();
var self = this; }.bind(this));
contentPrefs.getPref(aURI, this.name, function (aResult) { return;
// Check that we're still where we expect to be in case this took a while.
// Null check currentURI, since the window may have been destroyed before
// we were called.
if (browser.currentURI && aURI.equals(browser.currentURI)) {
self._applyPrefToSetting(aResult, browser);
}
});
} }
let value = undefined;
this._cps2.getByDomainAndName(aURI.spec, this.name, ctxt, {
handleResult: function (resultPref) value = resultPref.value,
handleCompletion: function () {
if (browser.currentURI &&
this._cps2.extractDomain(browser.currentURI.spec) ==
this._cps2.extractDomain(aURI.spec)) {
this._applyPrefToSetting(value, browser, function () {
this._notifyOnLocationChange();
}.bind(this));
}
}.bind(this)
});
}, },
// update state of zoom type menu item // update state of zoom type menu item
@ -246,23 +267,43 @@ var FullZoom = {
//**************************************************************************// //**************************************************************************//
// Setting & Pref Manipulation // Setting & Pref Manipulation
reduce: function FullZoom_reduce() { /**
* Reduces the zoom level of the page in the current browser.
*
* @param callback Optional. If given, it's asynchronously called when the
* zoom update completes.
*/
reduce: function FullZoom_reduce(callback) {
ZoomManager.reduce(); ZoomManager.reduce();
this._applySettingToPref(); this._applySettingToPref(false, callback);
}, },
enlarge: function FullZoom_enlarge() { /**
* Enlarges the zoom level of the page in the current browser.
*
* @param callback Optional. If given, it's asynchronously called when the
* zoom update completes.
*/
enlarge: function FullZoom_enlarge(callback) {
ZoomManager.enlarge(); ZoomManager.enlarge();
this._applySettingToPref(); this._applySettingToPref(false, callback);
}, },
reset: function FullZoom_reset() { /**
if (typeof this.globalValue != "undefined") * Sets the zoom level of the page in the current browser to the global zoom
ZoomManager.zoom = this.globalValue; * level.
else *
ZoomManager.reset(); * @param callback Optional. If given, it's asynchronously called when the
* zoom update completes.
this._removePref(); */
reset: function FullZoom_reset(callback) {
this._getGlobalValue(gBrowser.contentWindow, function (value) {
if (value === undefined)
ZoomManager.reset();
else
ZoomManager.zoom = value;
this._removePref(callback);
});
}, },
/** /**
@ -283,38 +324,91 @@ var FullZoom = {
* So when we apply new zoom values to the browser, we simply set the zoom. * So when we apply new zoom values to the browser, we simply set the zoom.
* We don't check first to see if the new value is the same as the current * We don't check first to see if the new value is the same as the current
* one. * one.
**/ *
_applyPrefToSetting: function FullZoom__applyPrefToSetting(aValue, aBrowser) { * @param aValue The zoom level value.
if ((!this.siteSpecific) || gInPrintPreviewMode) * @param aBrowser The browser containing the page whose zoom level is to be
* set. If falsey, the currently selected browser is used.
* @param aCallback Optional. If given, it's asynchronously called when
* complete.
*/
_applyPrefToSetting: function FullZoom__applyPrefToSetting(aValue, aBrowser, aCallback) {
if (!this.siteSpecific || gInPrintPreviewMode) {
this._executeSoon(aCallback);
return; return;
}
var browser = aBrowser || (gBrowser && gBrowser.selectedBrowser); var browser = aBrowser || (gBrowser && gBrowser.selectedBrowser);
try { if (browser.contentDocument.mozSyntheticDocument) {
if (browser.contentDocument.mozSyntheticDocument) this._executeSoon(aCallback);
return;
if (typeof aValue != "undefined")
ZoomManager.setZoomForBrowser(browser, this._ensureValid(aValue));
else if (typeof this.globalValue != "undefined")
ZoomManager.setZoomForBrowser(browser, this.globalValue);
else
ZoomManager.setZoomForBrowser(browser, 1);
}
catch(ex) {}
},
_applySettingToPref: function FullZoom__applySettingToPref() {
if (!this.siteSpecific || gInPrintPreviewMode ||
content.document.mozSyntheticDocument)
return; return;
}
var zoomLevel = ZoomManager.zoom; if (aValue !== undefined) {
getContentPrefs(gBrowser.contentDocument.defaultView).setPref(gBrowser.currentURI, this.name, zoomLevel); ZoomManager.setZoomForBrowser(browser, this._ensureValid(aValue));
this._executeSoon(aCallback);
return;
}
this._getGlobalValue(browser.contentWindow, function (value) {
if (gBrowser.selectedBrowser == browser)
ZoomManager.setZoomForBrowser(browser, value === undefined ? 1 : value);
this._executeSoon(aCallback);
});
}, },
_removePref: function FullZoom__removePref() { /**
if (!(content.document.mozSyntheticDocument)) * Saves the zoom level of the page in the current browser to the content
getContentPrefs(gBrowser.contentDocument.defaultView).removePref(gBrowser.currentURI, this.name); * prefs store.
*
* @param suppressZoomChange Because this method sets a content preference,
* our onContentPrefSet method is called as a
* result, which updates the current zoom level.
* If suppressZoomChange is true, then the next
* call to onContentPrefSet will not update the
* zoom level.
* @param callback Optional. If given, it's asynchronously called
* when done.
*/
_applySettingToPref: function FullZoom__applySettingToPref(suppressZoomChange, callback) {
if (!this.siteSpecific ||
gInPrintPreviewMode ||
content.document.mozSyntheticDocument) {
this._executeSoon(callback);
return;
}
this._cps2.set(gBrowser.currentURI.spec, this.name, ZoomManager.zoom,
this._loadContextFromWindow(gBrowser.contentWindow), {
handleCompletion: function () {
if (suppressZoomChange)
// The content preference service calls observers after callbacks, and
// it calls both in the same turn of the event loop. onContentPrefSet
// will therefore be called next, in the same turn of the event loop,
// and it will check this flag.
this._ignoreNextOnContentPrefSet = true;
if (callback)
callback();
}.bind(this)
});
},
/**
* Removes from the content prefs store the zoom level of the current browser.
*
* @param callback Optional. If given, it's asynchronously called when done.
*/
_removePref: function FullZoom__removePref(callback) {
if (content.document.mozSyntheticDocument) {
this._executeSoon(callback);
return;
}
let ctxt = this._loadContextFromWindow(gBrowser.contentWindow);
this._cps2.removeByDomainAndName(gBrowser.currentURI.spec, this.name, ctxt, {
handleCompletion: function () {
if (callback)
callback();
}
});
}, },
@ -322,6 +416,8 @@ var FullZoom = {
// Utilities // Utilities
_ensureValid: function FullZoom__ensureValid(aValue) { _ensureValid: function FullZoom__ensureValid(aValue) {
// Note that undefined is a valid value for aValue that indicates a known-
// not-to-exist value.
if (isNaN(aValue)) if (isNaN(aValue))
return 1; return 1;
@ -332,5 +428,68 @@ var FullZoom = {
return ZoomManager.MAX; return ZoomManager.MAX;
return aValue; return aValue;
} },
/**
* Gets the global browser.content.full-zoom content preference.
*
* WARNING: callback may be called synchronously or asynchronously. The
* reason is that it's usually desirable to avoid turns of the event loop
* where possible, since they can lead to visible, jarring jumps in zoom
* level. It's not always possible to avoid them, though. As a convenience,
* then, this method takes a callback and returns nothing.
*
* @param window The content window pertaining to the zoom.
* @param callback Synchronously or asynchronously called when done. It's
* bound to this object (FullZoom) and passed the preference
* value.
*/
_getGlobalValue: function FullZoom__getGlobalValue(window, callback) {
// * !("_globalValue" in this) => global value not yet cached.
// * this._globalValue === undefined => global value known not to exist.
// * Otherwise, this._globalValue is a number, the global value.
if ("_globalValue" in this) {
callback.call(this, this._globalValue);
return;
}
let value = undefined;
this._cps2.getGlobal(this.name, this._loadContextFromWindow(window), {
handleResult: function (pref) value = pref.value,
handleCompletion: function () {
this._globalValue = this._ensureValid(value);
callback.call(this, this._globalValue);
}.bind(this)
});
},
/**
* Gets the load context from the given window.
*
* @param window The window whose load context will be returned.
* @return The nsILoadContext of the given window.
*/
_loadContextFromWindow: function FullZoom__loadContextFromWindow(window) {
return window.
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsILoadContext);
},
/**
* Asynchronously broadcasts a "browser-fullZoom:locationChange" notification
* so that tests can select tabs, load pages, etc. and be notified when the
* zoom levels on those pages change. The notification is always asynchronous
* so that observers are guaranteed a consistent behavior.
*/
_notifyOnLocationChange: function FullZoom__notifyOnLocationChange() {
this._executeSoon(function () {
Services.obs.notifyObservers(null, "browser-fullZoom:locationChange", "");
});
},
_executeSoon: function FullZoom__executeSoon(callback) {
if (!callback)
return;
Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
},
}; };

View File

@ -2745,6 +2745,8 @@ let BrowserOnClick = {
let isMalware = /e=malwareBlocked/.test(aOwnerDoc.documentURI); let isMalware = /e=malwareBlocked/.test(aOwnerDoc.documentURI);
let bucketName = isMalware ? "WARNING_MALWARE_PAGE_":"WARNING_PHISHING_PAGE_"; let bucketName = isMalware ? "WARNING_MALWARE_PAGE_":"WARNING_PHISHING_PAGE_";
let nsISecTel = Ci.nsISecurityUITelemetry; let nsISecTel = Ci.nsISecurityUITelemetry;
let isIframe = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView);
bucketName += isIframe ? "TOP_" : "FRAME_";
switch (elmId) { switch (elmId) {
case "getMeOutButton": case "getMeOutButton":

View File

@ -360,8 +360,8 @@ Sanitizer.prototype = {
// Clear site-specific settings like page-zoom level // Clear site-specific settings like page-zoom level
var cps = Components.classes["@mozilla.org/content-pref/service;1"] var cps = Components.classes["@mozilla.org/content-pref/service;1"]
.getService(Components.interfaces.nsIContentPrefService); .getService(Components.interfaces.nsIContentPrefService2);
cps.removeGroupedPrefs(null); cps.removeAllDomains(null);
// Clear "Never remember passwords for this site", which is not handled by // Clear "Never remember passwords for this site", which is not handled by
// the permission manager // the permission manager

View File

@ -8,136 +8,82 @@ const FORWARD = 1;
function test() { function test() {
waitForExplicitFinish(); waitForExplicitFinish();
gTab1 = gBrowser.addTab(gTestPage); Task.spawn(function () {
gTab2 = gBrowser.addTab(); gTab1 = gBrowser.addTab(gTestPage);
gTab3 = gBrowser.addTab(); gTab2 = gBrowser.addTab();
gBrowser.selectedTab = gTab1; gTab3 = gBrowser.addTab();
load(gTab1, gTestPage, function () { yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
load(gTab2, gTestPage, secondPageLoaded); yield FullZoomHelper.load(gTab1, gTestPage);
}); yield FullZoomHelper.load(gTab2, gTestPage);
}).then(secondPageLoaded, FullZoomHelper.failAndContinue(finish));
} }
function secondPageLoaded() { function secondPageLoaded() {
zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1"); Task.spawn(function () {
zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1"); FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
zoomTest(gTab3, 1, "Initial zoom of tab 3 should be 1"); FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
FullZoomHelper.zoomTest(gTab3, 1, "Initial zoom of tab 3 should be 1");
// Now have three tabs, two with the test page, one blank. Tab 1 is selected // Now have three tabs, two with the test page, one blank. Tab 1 is selected
// Zoom tab 1 // Zoom tab 1
FullZoom.enlarge(); yield FullZoomHelper.enlarge();
gLevel = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1)); gLevel = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1));
ok(gLevel > 1, "New zoom for tab 1 should be greater than 1"); ok(gLevel > 1, "New zoom for tab 1 should be greater than 1");
zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2"); FullZoomHelper.zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2");
zoomTest(gTab3, 1, "Zooming tab 1 should not affect tab 3"); FullZoomHelper.zoomTest(gTab3, 1, "Zooming tab 1 should not affect tab 3");
load(gTab3, gTestPage, thirdPageLoaded); yield FullZoomHelper.load(gTab3, gTestPage);
}).then(thirdPageLoaded, FullZoomHelper.failAndContinue(finish));
} }
function thirdPageLoaded() { function thirdPageLoaded() {
zoomTest(gTab1, gLevel, "Tab 1 should still be zoomed"); Task.spawn(function () {
zoomTest(gTab2, 1, "Tab 2 should still not be affected"); FullZoomHelper.zoomTest(gTab1, gLevel, "Tab 1 should still be zoomed");
zoomTest(gTab3, gLevel, "Tab 3 should have zoomed as it was loading in the background"); FullZoomHelper.zoomTest(gTab2, 1, "Tab 2 should still not be affected");
FullZoomHelper.zoomTest(gTab3, gLevel, "Tab 3 should have zoomed as it was loading in the background");
// Switching to tab 2 should update its zoom setting. // Switching to tab 2 should update its zoom setting.
afterZoom(function() { yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
zoomTest(gTab1, gLevel, "Tab 1 should still be zoomed"); FullZoomHelper.zoomTest(gTab1, gLevel, "Tab 1 should still be zoomed");
zoomTest(gTab2, gLevel, "Tab 2 should be zoomed now"); FullZoomHelper.zoomTest(gTab2, gLevel, "Tab 2 should be zoomed now");
zoomTest(gTab3, gLevel, "Tab 3 should still be zoomed"); FullZoomHelper.zoomTest(gTab3, gLevel, "Tab 3 should still be zoomed");
load(gTab1, gTestImage, imageLoaded); yield FullZoomHelper.load(gTab1, gTestImage);
}); }).then(imageLoaded, FullZoomHelper.failAndContinue(finish));
gBrowser.selectedTab = gTab2;
} }
function imageLoaded() { function imageLoaded() {
zoomTest(gTab1, 1, "Zoom should be 1 when image was loaded in the background"); Task.spawn(function () {
gBrowser.selectedTab = gTab1; FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 when image was loaded in the background");
zoomTest(gTab1, 1, "Zoom should still be 1 when tab with image is selected"); yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
FullZoomHelper.zoomTest(gTab1, 1, "Zoom should still be 1 when tab with image is selected");
executeSoon(imageZoomSwitch); }).then(imageZoomSwitch, FullZoomHelper.failAndContinue(finish));
} }
function imageZoomSwitch() { function imageZoomSwitch() {
navigate(BACK, function () { Task.spawn(function () {
navigate(FORWARD, function () { yield FullZoomHelper.navigate(BACK);
zoomTest(gTab1, 1, "Tab 1 should not be zoomed when an image loads"); yield FullZoomHelper.navigate(FORWARD);
FullZoomHelper.zoomTest(gTab1, 1, "Tab 1 should not be zoomed when an image loads");
afterZoom(function() { yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
zoomTest(gTab1, 1, "Tab 1 should still not be zoomed when deselected"); FullZoomHelper.zoomTest(gTab1, 1, "Tab 1 should still not be zoomed when deselected");
finishTest(); }).then(finishTest, FullZoomHelper.failAndContinue(finish));
});
gBrowser.selectedTab = gTab2;
});
});
} }
var finishTestStarted = false; var finishTestStarted = false;
function finishTest() { function finishTest() {
ok(!finishTestStarted, "finishTest called more than once"); Task.spawn(function () {
finishTestStarted = true; ok(!finishTestStarted, "finishTest called more than once");
gBrowser.selectedTab = gTab1; finishTestStarted = true;
FullZoom.reset(); yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
gBrowser.removeTab(gTab1); yield FullZoomHelper.reset();
FullZoom.reset(); gBrowser.removeTab(gTab1);
gBrowser.removeTab(gTab2); yield FullZoomHelper.reset();
FullZoom.reset(); gBrowser.removeTab(gTab2);
gBrowser.removeTab(gTab3); yield FullZoomHelper.reset();
finish(); gBrowser.removeTab(gTab3);
} }).then(finish, FullZoomHelper.failAndContinue(finish));
function zoomTest(tab, val, msg) {
is(ZoomManager.getZoomForBrowser(tab.linkedBrowser), val, msg);
}
function load(tab, url, cb) {
let didLoad = false;
let didZoom = false;
tab.linkedBrowser.addEventListener("load", function (event) {
event.currentTarget.removeEventListener("load", arguments.callee, true);
didLoad = true;
if (didZoom)
executeSoon(cb);
}, true);
afterZoom(function() {
didZoom = true;
if (didLoad)
executeSoon(cb);
});
tab.linkedBrowser.loadURI(url);
}
function navigate(direction, cb) {
let didPs = false;
let didZoom = false;
gBrowser.addEventListener("pageshow", function (event) {
gBrowser.removeEventListener("pageshow", arguments.callee, true);
didPs = true;
if (didZoom)
executeSoon(cb);
}, true);
afterZoom(function() {
didZoom = true;
if (didPs)
executeSoon(cb);
});
if (direction == BACK)
gBrowser.goBack();
else if (direction == FORWARD)
gBrowser.goForward();
}
function afterZoom(cb) {
let oldSZFB = ZoomManager.setZoomForBrowser;
ZoomManager.setZoomForBrowser = function(browser, value) {
oldSZFB.call(ZoomManager, browser, value);
ZoomManager.setZoomForBrowser = oldSZFB;
executeSoon(cb);
};
} }

View File

@ -1,62 +1,42 @@
var tabElm, zoomLevel; var tabElm, zoomLevel;
function start_test_prefNotSet() { function start_test_prefNotSet() {
is(ZoomManager.zoom, 1, "initial zoom level should be 1"); Task.spawn(function () {
FullZoom.enlarge(); is(ZoomManager.zoom, 1, "initial zoom level should be 1");
yield FullZoomHelper.enlarge();
//capture the zoom level to test later //capture the zoom level to test later
zoomLevel = ZoomManager.zoom; zoomLevel = ZoomManager.zoom;
isnot(zoomLevel, 1, "zoom level should have changed"); isnot(zoomLevel, 1, "zoom level should have changed");
afterZoomAndLoad(continue_test_prefNotSet); yield FullZoomHelper.load(gBrowser.selectedTab, "http://mochi.test:8888/browser/browser/base/content/test/moz.png");
content.location = }).then(continue_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
"http://mochi.test:8888/browser/browser/base/content/test/moz.png";
} }
function continue_test_prefNotSet () { function continue_test_prefNotSet () {
is(ZoomManager.zoom, 1, "zoom level pref should not apply to an image"); Task.spawn(function () {
FullZoom.reset(); is(ZoomManager.zoom, 1, "zoom level pref should not apply to an image");
yield FullZoomHelper.reset();
afterZoomAndLoad(end_test_prefNotSet); yield FullZoomHelper.load(gBrowser.selectedTab, "http://mochi.test:8888/browser/browser/base/content/test/zoom_test.html");
content.location = }).then(end_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
"http://mochi.test:8888/browser/browser/base/content/test/zoom_test.html";
} }
function end_test_prefNotSet() { function end_test_prefNotSet() {
is(ZoomManager.zoom, zoomLevel, "the zoom level should have persisted"); Task.spawn(function () {
is(ZoomManager.zoom, zoomLevel, "the zoom level should have persisted");
// Reset the zoom so that other tests have a fresh zoom level // Reset the zoom so that other tests have a fresh zoom level
FullZoom.reset(); yield FullZoomHelper.reset();
gBrowser.removeCurrentTab(); gBrowser.removeCurrentTab();
finish(); }).then(finish, FullZoomHelper.failAndContinue(finish));
} }
function test() { function test() {
waitForExplicitFinish(); waitForExplicitFinish();
tabElm = gBrowser.addTab(); Task.spawn(function () {
gBrowser.selectedTab = tabElm; tabElm = gBrowser.addTab();
yield FullZoomHelper.selectTabAndWaitForLocationChange(tabElm);
afterZoomAndLoad(start_test_prefNotSet); yield FullZoomHelper.load(tabElm, "http://mochi.test:8888/browser/browser/base/content/test/zoom_test.html");
content.location = }).then(start_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
"http://mochi.test:8888/browser/browser/base/content/test/zoom_test.html";
}
function afterZoomAndLoad(cb) {
let didLoad = false;
let didZoom = false;
tabElm.linkedBrowser.addEventListener("load", function() {
tabElm.linkedBrowser.removeEventListener("load", arguments.callee, true);
didLoad = true;
if (didZoom)
executeSoon(cb);
}, true);
let oldSZFB = ZoomManager.setZoomForBrowser;
ZoomManager.setZoomForBrowser = function(browser, value) {
oldSZFB.call(ZoomManager, browser, value);
ZoomManager.setZoomForBrowser = oldSZFB;
didZoom = true;
if (didLoad)
executeSoon(cb);
};
} }

View File

@ -1,49 +1,32 @@
function test() { function test() {
waitForExplicitFinish(); waitForExplicitFinish();
let testPage = "http://example.org/browser/browser/base/content/test/dummy_page.html"; Task.spawn(function () {
let tab1 = gBrowser.selectedTab = gBrowser.addTab(); let testPage = "http://example.org/browser/browser/base/content/test/dummy_page.html";
tab1.linkedBrowser.addEventListener("load", (function(event) { let tab1 = gBrowser.addTab();
event.currentTarget.removeEventListener("load", arguments.callee, true); yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
yield FullZoomHelper.load(tab1, testPage);
let tab2 = gBrowser.addTab(); let tab2 = gBrowser.addTab();
tab2.linkedBrowser.addEventListener("load", (function(event) { yield FullZoomHelper.load(tab2, testPage);
event.currentTarget.removeEventListener("load", arguments.callee, true);
FullZoom.enlarge(); yield FullZoomHelper.enlarge();
let tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser); let tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
afterZoom(function() { yield FullZoomHelper.selectTabAndWaitForLocationChange(tab2);
let tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser); let tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser);
is(tab2Zoom, tab1Zoom, "Zoom should affect background tabs"); is(tab2Zoom, tab1Zoom, "Zoom should affect background tabs");
gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", false); gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", false);
FullZoom.reset(); yield FullZoomHelper.reset();
gBrowser.selectedTab = tab1; gBrowser.selectedTab = tab1;
tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser); tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser); tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser);
isnot(tab1Zoom, tab2Zoom, "Zoom should not affect background tabs"); isnot(tab1Zoom, tab2Zoom, "Zoom should not affect background tabs");
if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs")) if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs"))
gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs"); gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs");
gBrowser.removeTab(tab1); gBrowser.removeTab(tab1);
gBrowser.removeTab(tab2); gBrowser.removeTab(tab2);
finish(); }).then(finish, FullZoomHelper.failAndContinue(finish));
});
gBrowser.selectedTab = tab2;
}), true);
tab2.linkedBrowser.loadURI(testPage);
}), true);
content.location = testPage;
}
function afterZoom(cb) {
let oldAPTS = FullZoom._applyPrefToSetting;
FullZoom._applyPrefToSetting = function(value, browser) {
if (!value)
value = undefined;
oldAPTS.call(FullZoom, value, browser);
FullZoom._applyPrefToSetting = oldAPTS;
executeSoon(cb);
};
} }

View File

@ -13,19 +13,22 @@ function test() {
const TEST_PAGE_URL = 'data:text/html,<body><iframe src=""></iframe></body>'; const TEST_PAGE_URL = 'data:text/html,<body><iframe src=""></iframe></body>';
const TEST_IFRAME_URL = "http://test2.example.org/"; const TEST_IFRAME_URL = "http://test2.example.org/";
// Prepare the test tab Task.spawn(function () {
gBrowser.selectedTab = gBrowser.addTab(); // Prepare the test tab
let testBrowser = gBrowser.selectedBrowser; let tab = gBrowser.addTab();
yield FullZoomHelper.selectTabAndWaitForLocationChange(tab);
testBrowser.addEventListener("load", function () { let testBrowser = tab.linkedBrowser;
testBrowser.removeEventListener("load", arguments.callee, true);
yield FullZoomHelper.load(tab, TEST_PAGE_URL);
// Change the zoom level and then save it so we can compare it to the level // Change the zoom level and then save it so we can compare it to the level
// after loading the sub-document. // after loading the sub-document.
FullZoom.enlarge(); yield FullZoomHelper.enlarge();
var zoomLevel = ZoomManager.zoom; var zoomLevel = ZoomManager.zoom;
// Start the sub-document load. // Start the sub-document load.
let deferred = Promise.defer();
executeSoon(function () { executeSoon(function () {
testBrowser.addEventListener("load", function (e) { testBrowser.addEventListener("load", function (e) {
testBrowser.removeEventListener("load", arguments.callee, true); testBrowser.removeEventListener("load", arguments.callee, true);
@ -34,11 +37,10 @@ function test() {
is(ZoomManager.zoom, zoomLevel, "zoom is retained after sub-document load"); is(ZoomManager.zoom, zoomLevel, "zoom is retained after sub-document load");
gBrowser.removeCurrentTab(); gBrowser.removeCurrentTab();
finish(); deferred.resolve();
}, true); }, true);
content.document.querySelector("iframe").src = TEST_IFRAME_URL; content.document.querySelector("iframe").src = TEST_IFRAME_URL;
}); });
}, true); yield deferred.promise;
}).then(finish, FullZoomHelper.failAndContinue(finish));
content.location = TEST_PAGE_URL;
} }

View File

@ -4,59 +4,36 @@
const TEST_PAGE = "/browser/browser/base/content/test/dummy_page.html"; const TEST_PAGE = "/browser/browser/base/content/test/dummy_page.html";
var gTestTab, gBgTab, gTestZoom; var gTestTab, gBgTab, gTestZoom;
function afterZoomAndLoad(aCallback, aTab) {
let didLoad = false;
let didZoom = false;
aTab.linkedBrowser.addEventListener("load", function() {
aTab.linkedBrowser.removeEventListener("load", arguments.callee, true);
didLoad = true;
if (didZoom)
executeSoon(aCallback);
}, true);
let oldAPTS = FullZoom._applyPrefToSetting;
FullZoom._applyPrefToSetting = function(value, browser) {
if (!value)
value = undefined;
oldAPTS.call(FullZoom, value, browser);
// Don't reset _applyPrefToSetting until we've seen the about:blank load(s)
if (browser && browser.currentURI.spec.startsWith("http:")) {
FullZoom._applyPrefToSetting = oldAPTS;
didZoom = true;
}
if (didLoad && didZoom)
executeSoon(aCallback);
};
}
function testBackgroundLoad() { function testBackgroundLoad() {
is(ZoomManager.zoom, gTestZoom, "opening a background tab should not change foreground zoom"); Task.spawn(function () {
is(ZoomManager.zoom, gTestZoom, "opening a background tab should not change foreground zoom");
gBrowser.removeTab(gBgTab); gBrowser.removeTab(gBgTab);
FullZoom.reset(); yield FullZoomHelper.reset();
gBrowser.removeTab(gTestTab); gBrowser.removeTab(gTestTab);
}).then(finish, FullZoomHelper.failAndContinue(finish));
finish();
} }
function testInitialZoom() { function testInitialZoom() {
is(ZoomManager.zoom, 1, "initial zoom level should be 1"); Task.spawn(function () {
FullZoom.enlarge(); is(ZoomManager.zoom, 1, "initial zoom level should be 1");
yield FullZoomHelper.enlarge();
gTestZoom = ZoomManager.zoom; gTestZoom = ZoomManager.zoom;
isnot(gTestZoom, 1, "zoom level should have changed"); isnot(gTestZoom, 1, "zoom level should have changed");
afterZoomAndLoad(testBackgroundLoad, gBgTab = gBrowser.addTab();
gBgTab = gBrowser.loadOneTab("http://mochi.test:8888" + TEST_PAGE, yield FullZoomHelper.load(gBgTab, "http://mochi.test:8888" + TEST_PAGE);
{inBackground: true})); }).then(testBackgroundLoad, FullZoomHelper.failAndContinue(finish));
} }
function test() { function test() {
waitForExplicitFinish(); waitForExplicitFinish();
gTestTab = gBrowser.addTab(); Task.spawn(function () {
gBrowser.selectedTab = gTestTab; gTestTab = gBrowser.addTab();
yield FullZoomHelper.selectTabAndWaitForLocationChange(gTestTab);
afterZoomAndLoad(testInitialZoom, gTestTab); yield FullZoomHelper.load(gTestTab, "http://example.org" + TEST_PAGE);
content.location = "http://example.org" + TEST_PAGE; }).then(testInitialZoom, FullZoomHelper.failAndContinue(finish));
} }

View File

@ -1,3 +1,5 @@
var tab;
function test() { function test() {
// ---------- // ----------
@ -5,53 +7,34 @@ function test() {
waitForExplicitFinish(); waitForExplicitFinish();
let oldOLC = FullZoom.onLocationChange;
FullZoom.onLocationChange = function(aURI, aIsTabSwitch, aBrowser) {
// Ignore calls that are not about tab switching on this test
if (aIsTabSwitch)
oldOLC.call(FullZoom, aURI, aIsTabSwitch, aBrowser);
};
gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", true); gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", true);
gPrefService.setBoolPref("browser.zoom.siteSpecific", true); gPrefService.setBoolPref("browser.zoom.siteSpecific", true);
let oldAPTS = FullZoom._applyPrefToSetting;
let uri = "http://example.org/browser/browser/base/content/test/dummy_page.html"; let uri = "http://example.org/browser/browser/base/content/test/dummy_page.html";
let tab = gBrowser.addTab(); Task.spawn(function () {
tab.linkedBrowser.addEventListener("load", function(event) { tab = gBrowser.addTab();
tab.linkedBrowser.removeEventListener("load", arguments.callee, true); yield FullZoomHelper.load(tab, uri);
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Test - Trigger a tab switch that should update the zoom level // Test - Trigger a tab switch that should update the zoom level
FullZoom._applyPrefToSetting = function() { yield FullZoomHelper.selectTabAndWaitForLocationChange(tab);
ok(true, "applyPrefToSetting was called"); ok(true, "applyPrefToSetting was called");
endTest(); }).then(endTest, FullZoomHelper.failAndContinue(endTest));
}
gBrowser.selectedTab = tab;
}, true);
tab.linkedBrowser.loadURI(uri);
// -------------
// Test clean-up
function endTest() {
FullZoom._applyPrefToSetting = oldAPTS;
FullZoom.onLocationChange = oldOLC;
gBrowser.removeTab(tab);
oldAPTS = null;
oldOLC = null;
tab = null;
if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs"))
gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs");
if (gPrefService.prefHasUserValue("browser.zoom.siteSpecific"))
gPrefService.clearUserPref("browser.zoom.siteSpecific");
finish();
}
} }
// -------------
// Test clean-up
function endTest() {
gBrowser.removeTab(tab);
tab = null;
if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs"))
gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs");
if (gPrefService.prefHasUserValue("browser.zoom.siteSpecific"))
gPrefService.clearUserPref("browser.zoom.siteSpecific");
finish();
}

View File

@ -13,25 +13,22 @@ function test() {
gBrowser.removeTab(tab2); gBrowser.removeTab(tab2);
}); });
tab1 = gBrowser.addTab(TEST_IMAGE); Task.spawn(function () {
tab2 = gBrowser.addTab(); tab1 = gBrowser.addTab();
gBrowser.selectedTab = tab1; tab2 = gBrowser.addTab();
yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
yield FullZoomHelper.load(tab1, TEST_IMAGE);
tab1.linkedBrowser.addEventListener("load", function onload() {
tab1.linkedBrowser.removeEventListener("load", onload, true);
is(ZoomManager.zoom, 1, "initial zoom level for first should be 1"); is(ZoomManager.zoom, 1, "initial zoom level for first should be 1");
FullZoom.enlarge(); yield FullZoomHelper.enlarge();
let zoom = ZoomManager.zoom; let zoom = ZoomManager.zoom;
isnot(zoom, 1, "zoom level should have changed"); isnot(zoom, 1, "zoom level should have changed");
gBrowser.selectedTab = tab2; yield FullZoomHelper.selectTabAndWaitForLocationChange(tab2);
is(ZoomManager.zoom, 1, "initial zoom level for second tab should be 1"); is(ZoomManager.zoom, 1, "initial zoom level for second tab should be 1");
gBrowser.selectedTab = tab1; yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
is(ZoomManager.zoom, zoom, "zoom level for first tab should not have changed"); is(ZoomManager.zoom, zoom, "zoom level for first tab should not have changed");
}).then(finish, FullZoomHelper.failAndContinue(finish));
finish();
}, true);
} }

View File

@ -11,131 +11,71 @@ var gTab1, gTab2, gLevel1, gLevel2;
function test() { function test() {
waitForExplicitFinish(); waitForExplicitFinish();
gTab1 = gBrowser.addTab(); Task.spawn(function () {
gTab2 = gBrowser.addTab(); gTab1 = gBrowser.addTab();
gBrowser.selectedTab = gTab1; gTab2 = gBrowser.addTab();
load(gTab1, TEST_PAGE, function() { yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
load(gTab2, TEST_VIDEO, zoomTab1); yield FullZoomHelper.load(gTab1, TEST_PAGE);
}); yield FullZoomHelper.load(gTab2, TEST_VIDEO);
}).then(zoomTab1, FullZoomHelper.failAndContinue(finish));
} }
function zoomTab1() { function zoomTab1() {
is(gBrowser.selectedTab, gTab1, "Tab 1 is selected"); Task.spawn(function () {
zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1"); is(gBrowser.selectedTab, gTab1, "Tab 1 is selected");
zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1"); FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
FullZoom.enlarge(); yield FullZoomHelper.enlarge();
gLevel1 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1)); gLevel1 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1));
ok(gLevel1 > 1, "New zoom for tab 1 should be greater than 1"); ok(gLevel1 > 1, "New zoom for tab 1 should be greater than 1");
zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2"); FullZoomHelper.zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2");
gBrowser.selectedTab = gTab2; yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
zoomTest(gTab2, 1, "Tab 2 is still unzoomed after it is selected"); FullZoomHelper.zoomTest(gTab2, 1, "Tab 2 is still unzoomed after it is selected");
zoomTest(gTab1, gLevel1, "Tab 1 is still zoomed"); FullZoomHelper.zoomTest(gTab1, gLevel1, "Tab 1 is still zoomed");
}).then(zoomTab2, FullZoomHelper.failAndContinue(finish));
zoomTab2();
} }
function zoomTab2() { function zoomTab2() {
is(gBrowser.selectedTab, gTab2, "Tab 2 is selected"); Task.spawn(function () {
is(gBrowser.selectedTab, gTab2, "Tab 2 is selected");
FullZoom.reduce(); yield FullZoomHelper.reduce();
let gLevel2 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab2)); let gLevel2 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab2));
ok(gLevel2 < 1, "New zoom for tab 2 should be less than 1"); ok(gLevel2 < 1, "New zoom for tab 2 should be less than 1");
zoomTest(gTab1, gLevel1, "Zooming tab 2 should not affect tab 1"); FullZoomHelper.zoomTest(gTab1, gLevel1, "Zooming tab 2 should not affect tab 1");
afterZoom(function() { yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
zoomTest(gTab1, gLevel1, "Tab 1 should have the same zoom after it's selected"); FullZoomHelper.zoomTest(gTab1, gLevel1, "Tab 1 should have the same zoom after it's selected");
}).then(testNavigation, FullZoomHelper.failAndContinue(finish));
testNavigation();
});
gBrowser.selectedTab = gTab1;
} }
function testNavigation() { function testNavigation() {
load(gTab1, TEST_VIDEO, function() { Task.spawn(function () {
zoomTest(gTab1, 1, "Zoom should be 1 when a video was loaded"); yield FullZoomHelper.load(gTab1, TEST_VIDEO);
navigate(BACK, function() { FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 when a video was loaded");
zoomTest(gTab1, gLevel1, "Zoom should be restored when a page is loaded"); yield FullZoomHelper.navigate(FullZoomHelper.BACK);
navigate(FORWARD, function() { FullZoomHelper.zoomTest(gTab1, gLevel1, "Zoom should be restored when a page is loaded");
zoomTest(gTab1, 1, "Zoom should be 1 again when navigating back to a video"); yield FullZoomHelper.navigate(FullZoomHelper.FORWARD);
finishTest(); FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 again when navigating back to a video");
}); }).then(finishTest, FullZoomHelper.failAndContinue(finish));
});
});
} }
var finishTestStarted = false; var finishTestStarted = false;
function finishTest() { function finishTest() {
ok(!finishTestStarted, "finishTest called more than once"); Task.spawn(function () {
finishTestStarted = true; ok(!finishTestStarted, "finishTest called more than once");
finishTestStarted = true;
gBrowser.selectedTab = gTab1; yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
FullZoom.reset(); yield FullZoomHelper.reset();
gBrowser.removeTab(gTab1); gBrowser.removeTab(gTab1);
yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
gBrowser.selectedTab = gTab2; yield FullZoomHelper.reset();
FullZoom.reset(); gBrowser.removeTab(gTab2);
gBrowser.removeTab(gTab2); }).then(finish, FullZoomHelper.failAndContinue(finish));
finish();
}
function zoomTest(tab, val, msg) {
is(ZoomManager.getZoomForBrowser(tab.linkedBrowser), val, msg);
}
function load(tab, url, cb) {
let didLoad = false;
let didZoom = false;
tab.linkedBrowser.addEventListener("load", function onload(event) {
event.currentTarget.removeEventListener("load", onload, true);
didLoad = true;
if (didZoom)
executeSoon(cb);
}, true);
afterZoom(function() {
didZoom = true;
if (didLoad)
executeSoon(cb);
});
tab.linkedBrowser.loadURI(url);
}
const BACK = 0;
const FORWARD = 1;
function navigate(direction, cb) {
let didPs = false;
let didZoom = false;
gBrowser.addEventListener("pageshow", function onpageshow(event) {
gBrowser.removeEventListener("pageshow", onpageshow, true);
didPs = true;
if (didZoom)
executeSoon(cb);
}, true);
afterZoom(function() {
didZoom = true;
if (didPs)
executeSoon(cb);
});
if (direction == BACK)
gBrowser.goBack();
else if (direction == FORWARD)
gBrowser.goForward();
}
function afterZoom(cb) {
let oldSZFB = ZoomManager.setZoomForBrowser;
ZoomManager.setZoomForBrowser = function(browser, value) {
oldSZFB.call(ZoomManager, browser, value);
ZoomManager.setZoomForBrowser = oldSZFB;
executeSoon(cb);
};
} }

View File

@ -261,3 +261,107 @@ function promiseHistoryClearedState(aURIs, aShouldBeCleared) {
return deferred.promise; return deferred.promise;
} }
let FullZoomHelper = {
selectTabAndWaitForLocationChange: function selectTabAndWaitForLocationChange(tab) {
let deferred = Promise.defer();
if (tab && gBrowser.selectedTab == tab) {
deferred.resolve();
return deferred.promise;
}
if (tab)
gBrowser.selectedTab = tab;
Services.obs.addObserver(function obs() {
Services.obs.removeObserver(obs, "browser-fullZoom:locationChange");
deferred.resolve();
}, "browser-fullZoom:locationChange", false);
return deferred.promise;
},
load: function load(tab, url) {
let deferred = Promise.defer();
let didLoad = false;
let didZoom = false;
tab.linkedBrowser.addEventListener("load", function (event) {
event.currentTarget.removeEventListener("load", arguments.callee, true);
didLoad = true;
if (didZoom)
deferred.resolve();
}, true);
// Don't select background tabs. That way tests can use this method on
// background tabs without having them automatically be selected. Just wait
// for the zoom to change on the current tab if it's `tab`.
if (tab == gBrowser.selectedTab) {
this.selectTabAndWaitForLocationChange(null).then(function () {
didZoom = true;
if (didLoad)
deferred.resolve();
});
}
else
didZoom = true;
tab.linkedBrowser.loadURI(url);
return deferred.promise;
},
zoomTest: function zoomTest(tab, val, msg) {
is(ZoomManager.getZoomForBrowser(tab.linkedBrowser), val, msg);
},
enlarge: function enlarge() {
let deferred = Promise.defer();
FullZoom.enlarge(function () deferred.resolve());
return deferred.promise;
},
reduce: function reduce() {
let deferred = Promise.defer();
FullZoom.reduce(function () deferred.resolve());
return deferred.promise;
},
reset: function reset() {
let deferred = Promise.defer();
FullZoom.reset(function () deferred.resolve());
return deferred.promise;
},
BACK: 0,
FORWARD: 1,
navigate: function navigate(direction) {
let deferred = Promise.defer();
let didPs = false;
let didZoom = false;
gBrowser.addEventListener("pageshow", function (event) {
gBrowser.removeEventListener("pageshow", arguments.callee, true);
didPs = true;
if (didZoom)
deferred.resolve();
}, true);
if (direction == this.BACK)
gBrowser.goBack();
else if (direction == this.FORWARD)
gBrowser.goForward();
this.selectTabAndWaitForLocationChange(null).then(function () {
didZoom = true;
if (didPs)
deferred.resolve();
});
return deferred.promise;
},
failAndContinue: function failAndContinue(func) {
return function (err) {
ok(false, err);
func();
};
},
};

View File

@ -274,29 +274,30 @@ var tests = [
function test_forget_site() { function test_forget_site() {
// click "Forget About This Site" button // click "Forget About This Site" button
gBrowser.contentDocument.getElementById("forget-site-button").doCommand(); gBrowser.contentDocument.getElementById("forget-site-button").doCommand();
waitForClearHistory(function() {
is(gSiteLabel.value, "", "site label cleared");
is(gSiteLabel.value, "", "site label cleared"); let allSitesItem = gBrowser.contentDocument.getElementById("all-sites-item");
is(gSitesList.selectedItem, allSitesItem,
"all sites item selected after forgetting selected site");
let allSitesItem = gBrowser.contentDocument.getElementById("all-sites-item"); // check to make sure site is gone from sites list
is(gSitesList.selectedItem, allSitesItem, let testSiteItem = getSiteItem(TEST_URI_2.host);
"all sites item selected after forgetting selected site"); ok(!testSiteItem, "site removed from sites list");
// check to make sure site is gone from sites list // check to make sure we forgot all permissions corresponding to site
let testSiteItem = getSiteItem(TEST_URI_2.host); for (let type in TEST_PERMS) {
ok(!testSiteItem, "site removed from sites list"); if (type == "password") {
ok(Services.logins.getLoginSavingEnabled(TEST_URI_2.prePath),
// check to make sure we forgot all permissions corresponding to site "password saving should be enabled by default");
for (let type in TEST_PERMS) { } else {
if (type == "password") { is(Services.perms.testPermissionFromPrincipal(TEST_PRINCIPAL_2, type), PERM_UNKNOWN,
ok(Services.logins.getLoginSavingEnabled(TEST_URI_2.prePath), type + " permission should not be set for test site 2");
"password saving should be enabled by default"); }
} else {
is(Services.perms.testPermissionFromPrincipal(TEST_PRINCIPAL_2, type), PERM_UNKNOWN,
type + " permission should not be set for test site 2");
} }
}
runNextTest(); runNextTest();
});
} }
]; ];

View File

@ -25,27 +25,29 @@ function test() {
let mozillaZoom = aWindow.ZoomManager.zoom; let mozillaZoom = aWindow.ZoomManager.zoom;
// change the zoom on the mozilla page // change the zoom on the mozilla page
aWindow.FullZoom.enlarge(); aWindow.FullZoom.enlarge(function () {
// make sure the zoom level has been changed // make sure the zoom level has been changed
isnot(aWindow.ZoomManager.zoom, mozillaZoom, "Zoom level can be changed"); isnot(aWindow.ZoomManager.zoom, mozillaZoom, "Zoom level can be changed");
mozillaZoom = aWindow.ZoomManager.zoom; mozillaZoom = aWindow.ZoomManager.zoom;
// switch to about: tab // switch to about: tab
aWindow.gBrowser.selectedTab = tabAbout; aWindow.gBrowser.selectedTab = tabAbout;
// switch back to mozilla tab // switch back to mozilla tab
aWindow.gBrowser.selectedTab = tabMozilla; aWindow.gBrowser.selectedTab = tabMozilla;
// make sure the zoom level has not changed // make sure the zoom level has not changed
is(aWindow.ZoomManager.zoom, mozillaZoom, is(aWindow.ZoomManager.zoom, mozillaZoom,
"Entering private browsing should not reset the zoom on a tab"); "Entering private browsing should not reset the zoom on a tab");
// cleanup // cleanup
aWindow.FullZoom.reset(); aWindow.FullZoom.reset(function () {
aWindow.gBrowser.removeTab(tabMozilla); aWindow.gBrowser.removeTab(tabMozilla);
aWindow.gBrowser.removeTab(tabAbout); aWindow.gBrowser.removeTab(tabAbout);
aWindow.close(); aWindow.close();
aCallback(); aCallback();
});
});
}, true); }, true);
mozillaBrowser.contentWindow.location = "about:mozilla"; mozillaBrowser.contentWindow.location = "about:mozilla";
}, true); }, true);

View File

@ -16,12 +16,14 @@ function test() {
aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true); aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
if (aIsZoomedWindow) { if (aIsZoomedWindow) {
// change the zoom on the blank page // change the zoom on the blank page
aWindow.FullZoom.enlarge(); aWindow.FullZoom.enlarge(function () {
isnot(aWindow.ZoomManager.zoom, 1, "Zoom level for about:blank should be changed"); isnot(aWindow.ZoomManager.zoom, 1, "Zoom level for about:blank should be changed");
} else { aCallback();
// make sure the zoom level is set to 1 });
is(aWindow.ZoomManager.zoom, 1, "Zoom level for about:privatebrowsing should be reset"); return;
} }
// make sure the zoom level is set to 1
is(aWindow.ZoomManager.zoom, 1, "Zoom level for about:privatebrowsing should be reset");
aCallback(); aCallback();
}, true); }, true);
@ -31,10 +33,18 @@ function test() {
function finishTest() { function finishTest() {
// cleanup // cleanup
let numWindows = windowsToReset.length;
if (!numWindows) {
finish();
return;
}
windowsToReset.forEach(function(win) { windowsToReset.forEach(function(win) {
win.FullZoom.reset(); win.FullZoom.reset(function onReset() {
numWindows--;
if (!numWindows)
finish();
});
}); });
finish();
} }
function testOnWindow(options, callback) { function testOnWindow(options, callback) {

View File

@ -4,7 +4,18 @@
Components.utils.import("resource://gre/modules/ForgetAboutSite.jsm"); Components.utils.import("resource://gre/modules/ForgetAboutSite.jsm");
function waitForClearHistory(aCallback) {
let observer = {
observe: function(aSubject, aTopic, aData) {
Services.obs.removeObserver(this, "browser:purge-domain-data");
setTimeout(aCallback, 0);
}
};
Services.obs.addObserver(observer, "browser:purge-domain-data", false);
}
function test() { function test() {
waitForExplicitFinish();
// utility functions // utility functions
function countClosedTabsByTitle(aClosedTabList, aTitle) function countClosedTabsByTitle(aClosedTabList, aTitle)
aClosedTabList.filter(function (aData) aData.title == aTitle).length; aClosedTabList.filter(function (aData) aData.title == aTitle).length;
@ -80,36 +91,38 @@ function test() {
// purge domain & check that we purged correctly for closed windows // purge domain & check that we purged correctly for closed windows
ForgetAboutSite.removeDataFromDomain("mozilla.org"); ForgetAboutSite.removeDataFromDomain("mozilla.org");
waitForClearHistory(function() {
let closedWindowData = JSON.parse(ss.getClosedWindowData());
let closedWindowData = JSON.parse(ss.getClosedWindowData()); // First set of tests for _closedWindows[0] - tests basics
let win = closedWindowData[0];
is(win.tabs.length, 1, "1 tab was removed");
is(countOpenTabsByTitle(win.tabs, FORGET), 0,
"The correct tab was removed");
is(countOpenTabsByTitle(win.tabs, REMEMBER), 1,
"The correct tab was remembered");
is(win.selected, 1, "Selected tab has changed");
is(win.title, REMEMBER, "The window title was correctly updated");
// First set of tests for _closedWindows[0] - tests basics // Test more complicated case
let win = closedWindowData[0]; win = closedWindowData[1];
is(win.tabs.length, 1, "1 tab was removed"); is(win.tabs.length, 3, "2 tabs were removed");
is(countOpenTabsByTitle(win.tabs, FORGET), 0, is(countOpenTabsByTitle(win.tabs, FORGET), 0,
"The correct tab was removed"); "The correct tabs were removed");
is(countOpenTabsByTitle(win.tabs, REMEMBER), 1, is(countOpenTabsByTitle(win.tabs, REMEMBER), 3,
"The correct tab was remembered"); "The correct tabs were remembered");
is(win.selected, 1, "Selected tab has changed"); is(win.selected, 3, "Selected tab has changed");
is(win.title, REMEMBER, "The window title was correctly updated"); is(win.title, REMEMBER, "The window title was correctly updated");
// Test more complicated case // Tests handling of _closedTabs
win = closedWindowData[1]; win = closedWindowData[2];
is(win.tabs.length, 3, "2 tabs were removed"); is(countClosedTabsByTitle(win._closedTabs, REMEMBER), 1,
is(countOpenTabsByTitle(win.tabs, FORGET), 0, "The correct number of tabs were removed, and the correct ones");
"The correct tabs were removed"); is(countClosedTabsByTitle(win._closedTabs, FORGET), 0,
is(countOpenTabsByTitle(win.tabs, REMEMBER), 3, "All tabs to be forgotten were indeed removed");
"The correct tabs were remembered");
is(win.selected, 3, "Selected tab has changed");
is(win.title, REMEMBER, "The window title was correctly updated");
// Tests handling of _closedTabs // restore pre-test state
win = closedWindowData[2]; ss.setBrowserState(oldState);
is(countClosedTabsByTitle(win._closedTabs, REMEMBER), 1, finish();
"The correct number of tabs were removed, and the correct ones"); });
is(countClosedTabsByTitle(win._closedTabs, FORGET), 0,
"All tabs to be forgotten were indeed removed");
// restore pre-test state
ss.setBrowserState(oldState);
} }

View File

@ -4,6 +4,16 @@
Components.utils.import("resource://gre/modules/ForgetAboutSite.jsm"); Components.utils.import("resource://gre/modules/ForgetAboutSite.jsm");
function waitForClearHistory(aCallback) {
let observer = {
observe: function(aSubject, aTopic, aData) {
Services.obs.removeObserver(this, "browser:purge-domain-data");
setTimeout(aCallback, 0);
}
};
Services.obs.addObserver(observer, "browser:purge-domain-data", false);
}
function test() { function test() {
/** Test for Bug 464199 **/ /** Test for Bug 464199 **/
@ -59,18 +69,19 @@ function test() {
"Everything is set up."); "Everything is set up.");
ForgetAboutSite.removeDataFromDomain("example.net"); ForgetAboutSite.removeDataFromDomain("example.net");
waitForClearHistory(function() {
closedTabs = JSON.parse(ss.getClosedTabData(newWin));
is(closedTabs.length, remember_count,
"The correct amout of tabs was removed");
is(countByTitle(closedTabs, FORGET), 0,
"All tabs to be forgotten were indeed removed");
is(countByTitle(closedTabs, REMEMBER), remember_count,
"... and tabs to be remembered weren't.");
closedTabs = JSON.parse(ss.getClosedTabData(newWin)); // clean up
is(closedTabs.length, remember_count, newWin.close();
"The correct amout of tabs was removed"); gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
is(countByTitle(closedTabs, FORGET), 0, finish();
"All tabs to be forgotten were indeed removed"); });
is(countByTitle(closedTabs, REMEMBER), remember_count,
"... and tabs to be remembered weren't.");
// clean up
newWin.close();
gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
finish();
}, false); }, false);
} }

View File

@ -12,6 +12,9 @@
const TEST_HTTPS_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html"; const TEST_HTTPS_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html";
var origBlockDisplay;
var origBlockActive;
function test() { function test() {
addTab("data:text/html;charset=utf8,Web Console mixed content test"); addTab("data:text/html;charset=utf8,Web Console mixed content test");
browser.addEventListener("load", onLoad, true); browser.addEventListener("load", onLoad, true);
@ -19,6 +22,10 @@ function test() {
function onLoad(aEvent) { function onLoad(aEvent) {
browser.removeEventListener("load", onLoad, true); browser.removeEventListener("load", onLoad, true);
origBlockDisplay = Services.prefs.getBoolPref("security.mixed_content.block_display_content");
origBlockActive = Services.prefs.getBoolPref("security.mixed_content.block_active_content")
Services.prefs.setBoolPref("security.mixed_content.block_display_content", false);
Services.prefs.setBoolPref("security.mixed_content.block_active_content", false);
openConsole(null, testMixedContent); openConsole(null, testMixedContent);
} }
@ -45,10 +52,10 @@ function testMixedContent(hud) {
is(warningNode.value, "[Mixed Content]", "Message text is accurate." ); is(warningNode.value, "[Mixed Content]", "Message text is accurate." );
testClickOpenNewTab(warningNode); testClickOpenNewTab(warningNode);
finishTest(); endTest();
}, },
failureFn: finishTest, failureFn: endTest,
} }
); );
@ -81,3 +88,9 @@ function testClickOpenNewTab(warningNode) {
window.openUILinkIn = oldOpenUILinkIn; window.openUILinkIn = oldOpenUILinkIn;
} }
function endTest() {
Services.prefs.setBoolPref("security.mixed_content.block_display_content", origBlockDisplay);
Services.prefs.setBoolPref("security.mixed_content.block_active_content", origBlockActive);
finishTest();
}

View File

@ -14,6 +14,14 @@ var FindHelperUI = {
_open: false, _open: false,
_status: null, _status: null,
/*
* Properties
*/
get isActive() {
return this._open;
},
get status() { get status() {
return this._status; return this._status;
}, },
@ -94,9 +102,12 @@ var FindHelperUI = {
}, },
show: function findHelperShow() { show: function findHelperShow() {
// Hide any menus
ContextUI.dismiss(); ContextUI.dismiss();
// Shutdown selection related ui
SelectionHelperUI.closeEditSession();
this._container.show(this); this._container.show(this);
this.search(this._textbox.value); this.search(this._textbox.value);
this._textbox.select(); this._textbox.select();

View File

@ -146,7 +146,7 @@ var TouchModule = {
// once we get omtc and the apzc. Currently though dblclick is delivered to // once we get omtc and the apzc. Currently though dblclick is delivered to
// content and triggers selection of text, so fire up the SelectionHelperUI // content and triggers selection of text, so fire up the SelectionHelperUI
// once selection is present. // once selection is present.
if (!SelectionHelperUI.isActive) { if (!SelectionHelperUI.isActive && !FindHelperUI.isActive) {
setTimeout(function () { setTimeout(function () {
SelectionHelperUI.attachEditSession(Browser.selectedTab.browser, SelectionHelperUI.attachEditSession(Browser.selectedTab.browser,
aEvent.clientX, aEvent.clientY); aEvent.clientX, aEvent.clientY);

View File

@ -124,8 +124,8 @@ Sanitizer.prototype = {
Services.perms.removeAll(); Services.perms.removeAll();
// Clear site-specific settings like page-zoom level // Clear site-specific settings like page-zoom level
var cps = Cc["@mozilla.org/content-pref/service;1"].getService(Ci.nsIContentPrefService); var cps = Cc["@mozilla.org/content-pref/service;1"].getService(Ci.nsIContentPrefService2);
cps.removeGroupedPrefs(); cps.removeAllDomains(null);
// Clear "Never remember passwords for this site", which is not handled by // Clear "Never remember passwords for this site", which is not handled by
// the permission manager // the permission manager

View File

@ -452,6 +452,10 @@ class Automation(object):
self.setupPermissionsDatabase(profileDir, self.setupPermissionsDatabase(profileDir,
{'allowXULXBL':[(l.host, 'noxul' not in l.options) for l in locations]}); {'allowXULXBL':[(l.host, 'noxul' not in l.options) for l in locations]});
# NOTE: For refactoring purposes we are temporarily storing these prefs
# in two locations. If you update a pref below, please also update
# it in source/testing/profiles/prefs_general.js.
# See bug 830430 for more details.
part = """\ part = """\
user_pref("browser.console.showInPanel", true); user_pref("browser.console.showInPanel", true);
user_pref("browser.dom.window.dump.enabled", true); user_pref("browser.dom.window.dump.enabled", true);

View File

@ -331,7 +331,7 @@ class B2GRemoteAutomation(Automation):
self.stdout_proc.run() self.stdout_proc.run()
if hasattr(self.stdout_proc, 'processOutput'): if hasattr(self.stdout_proc, 'processOutput'):
self.stdout_proc.processOutput() self.stdout_proc.processOutput()
self.stdout_proc.waitForFinish() self.stdout_proc.wait()
self.stdout_proc = None self.stdout_proc = None
@property @property

View File

@ -40,9 +40,8 @@ public class FennecNativeElement implements Element {
private boolean mClickSuccess; private boolean mClickSuccess;
public boolean click() { public boolean click() {
final SynchronousQueue syncQueue = new SynchronousQueue();
mClickSuccess = false; mClickSuccess = false;
mActivity.runOnUiThread( RobocopUtils.runOnUiThreadSync(mActivity,
new Runnable() { new Runnable() {
public void run() { public void run() {
View view = (View)mActivity.findViewById(mId); View view = (View)mActivity.findViewById(mId);
@ -57,32 +56,16 @@ public class FennecNativeElement implements Element {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR, FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR,
"click: unable to find view "+mId); "click: unable to find view "+mId);
} }
try {
syncQueue.put(new Object());
} catch (InterruptedException e) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR, e);
}
} }
}); });
try {
// Wait for the UiThread code to finish running
if (syncQueue.poll(MAX_WAIT_MS, TimeUnit.MILLISECONDS) == null) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR,
"click: time-out waiting for UI thread");
FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR);
}
} catch (InterruptedException e) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR, e);
}
return mClickSuccess; return mClickSuccess;
} }
private Object mText; private Object mText;
public String getText() { public String getText() {
final SynchronousQueue syncQueue = new SynchronousQueue();
mText = null; mText = null;
mActivity.runOnUiThread( RobocopUtils.runOnUiThreadSync(mActivity,
new Runnable() { new Runnable() {
public void run() { public void run() {
View v = mActivity.findViewById(mId); View v = mActivity.findViewById(mId);
@ -109,24 +92,9 @@ public class FennecNativeElement implements Element {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR, FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR,
"getText: unhandled type for view "+mId); "getText: unhandled type for view "+mId);
} }
try {
syncQueue.put(new Object());
} catch (InterruptedException e) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR, e);
}
} // end of run() method definition } // end of run() method definition
} // end of anonymous Runnable object instantiation } // end of anonymous Runnable object instantiation
); );
try {
// Wait for the UiThread code to finish running
if (syncQueue.poll(MAX_WAIT_MS, TimeUnit.MILLISECONDS) == null) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR,
"getText: time-out waiting for UI thread");
FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR);
}
} catch (InterruptedException e) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR, e);
}
if (mText == null) { if (mText == null) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.WARN, FennecNativeDriver.log(FennecNativeDriver.LogLevel.WARN,
"getText: Text is null for view "+mId); "getText: Text is null for view "+mId);
@ -138,33 +106,16 @@ public class FennecNativeElement implements Element {
private boolean mDisplayed; private boolean mDisplayed;
public boolean isDisplayed() { public boolean isDisplayed() {
final SynchronousQueue syncQueue = new SynchronousQueue();
mDisplayed = false; mDisplayed = false;
mActivity.runOnUiThread( RobocopUtils.runOnUiThreadSync(mActivity,
new Runnable() { new Runnable() {
public void run() { public void run() {
View view = (View)mActivity.findViewById(mId); View view = (View)mActivity.findViewById(mId);
if (view != null) { if (view != null) {
mDisplayed = true; mDisplayed = true;
} }
try {
syncQueue.put(new Object());
} catch (InterruptedException e) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR, e);
}
} }
}); });
try {
// Wait for the UiThread code to finish running
if (syncQueue.poll(MAX_WAIT_MS, TimeUnit.MILLISECONDS) == null) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR,
"isDisplayed: time-out waiting for UI thread");
FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR);
}
} catch (InterruptedException e) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR, e);
}
return mDisplayed; return mDisplayed;
} }
} }

View File

@ -35,6 +35,7 @@ _JAVA_HARNESS = \
FennecNativeDriver.java \ FennecNativeDriver.java \
FennecNativeElement.java \ FennecNativeElement.java \
RoboCopException.java \ RoboCopException.java \
RobocopUtils.java \
PaintedSurface.java \ PaintedSurface.java \
$(NULL) $(NULL)

View File

@ -0,0 +1,43 @@
#filter substitution
/* 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/. */
package @ANDROID_PACKAGE_NAME@;
import android.app.Activity;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public final class RobocopUtils {
private static final int MAX_WAIT_MS = 3000;
private RobocopUtils() {}
public static void runOnUiThreadSync(Activity activity, final Runnable runnable) {
final SynchronousQueue syncQueue = new SynchronousQueue();
activity.runOnUiThread(
new Runnable() {
public void run() {
runnable.run();
try {
syncQueue.put(new Object());
} catch (InterruptedException e) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR, e);
}
}
});
try {
// Wait for the UiThread code to finish running
if (syncQueue.poll(MAX_WAIT_MS, TimeUnit.MILLISECONDS) == null) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR,
"time-out waiting for UI thread");
FennecNativeDriver.logAllStackTraces(FennecNativeDriver.LogLevel.ERROR);
}
} catch (InterruptedException e) {
FennecNativeDriver.log(FennecNativeDriver.LogLevel.ERROR, e);
}
}
}

View File

@ -482,7 +482,8 @@ nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx)
return JS_TRUE; return JS_TRUE;
bool evalOK = true; bool evalOK = true;
rv = csp->GetAllowsEval(&evalOK); bool reportViolation = false;
rv = csp->GetAllowsEval(&reportViolation, &evalOK);
if (NS_FAILED(rv)) if (NS_FAILED(rv))
{ {
@ -490,9 +491,7 @@ nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx)
return JS_TRUE; // fail open to not break sites. return JS_TRUE; // fail open to not break sites.
} }
if (!evalOK) { if (reportViolation) {
// get the script filename, script sample, and line number
// to log with the violation
nsAutoString fileName; nsAutoString fileName;
unsigned lineNum = 0; unsigned lineNum = 0;
NS_NAMED_LITERAL_STRING(scriptSample, "call to eval() or related function blocked by CSP"); NS_NAMED_LITERAL_STRING(scriptSample, "call to eval() or related function blocked by CSP");
@ -503,7 +502,6 @@ nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx)
CopyUTF8toUTF16(nsDependentCString(file), fileName); CopyUTF8toUTF16(nsDependentCString(file), fileName);
} }
} }
csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL,
fileName, fileName,
scriptSample, scriptSample,

Some files were not shown because too many files have changed in this diff Show More