diff --git a/addon-sdk/source/app-extension/bootstrap.js b/addon-sdk/source/app-extension/bootstrap.js index 7bcaf81737ef..60dc6afb547d 100644 --- a/addon-sdk/source/app-extension/bootstrap.js +++ b/addon-sdk/source/app-extension/bootstrap.js @@ -20,7 +20,8 @@ const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')(); const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1']. getService(Ci.mozIJSSubScriptLoader); 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"]. getService(Ci.nsIXULAppInfo); const vc = Cc["@mozilla.org/xpcom/version-comparator;1"]. @@ -127,12 +128,21 @@ function startup(data, reasonCode) { if (name == 'addon-sdk') 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 // 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 if (options['is-sdk-bundled'] && - (vc.compare(appInfo.version, '21.0a1') < 0 || - options['force-use-bundled-sdk'])) { + (vc.compare(appInfo.version, '21.0a1') < 0 || useBundledSDK)) { // Maps sdk module folders to their resource folder paths[''] = prefixURI + 'addon-sdk/lib/'; // test.js is usually found in root commonjs or SDK_ROOT/lib/ folder, diff --git a/addon-sdk/source/app-extension/install.rdf b/addon-sdk/source/app-extension/install.rdf index b096e07af1d7..946fab813fcf 100644 --- a/addon-sdk/source/app-extension/install.rdf +++ b/addon-sdk/source/app-extension/install.rdf @@ -17,8 +17,8 @@ {ec8030f7-c20a-464f-9b0e-13a3a9e97384} - 18.0 - 21.0a1 + 19.0 + 22.0a1 diff --git a/addon-sdk/source/doc/dev-guide-source/credits.md b/addon-sdk/source/doc/dev-guide-source/credits.md index 7cd264461dbf..ac07630d0e11 100644 --- a/addon-sdk/source/doc/dev-guide-source/credits.md +++ b/addon-sdk/source/doc/dev-guide-source/credits.md @@ -135,7 +135,7 @@ We'd like to thank our many Jetpack project contributors! They include: ### P ### * Robert Pankowecki -* [Jamie Phelps](https://github.com/ongaeshi) +* [Jamie Phelps](https://github.com/jxpx777) * [Alexandre Poirot](https://github.com/ochameau) * Nickolay Ponomarev diff --git a/addon-sdk/source/doc/dev-guide-source/guides/content-scripts/using-postmessage.md b/addon-sdk/source/doc/dev-guide-source/guides/content-scripts/using-postmessage.md index 5a250ea8464c..a9784a115c68 100644 --- a/addon-sdk/source/doc/dev-guide-source/guides/content-scripts/using-postmessage.md +++ b/addon-sdk/source/doc/dev-guide-source/guides/content-scripts/using-postmessage.md @@ -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: - - -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. - - -
-Error: Couldn't find the worker to receive this message. The script may not be initialized yet, or may already have been unloaded.
-
- -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 ## You can use message events as an alternative to user-defined events: diff --git a/addon-sdk/source/doc/dev-guide-source/guides/events.md b/addon-sdk/source/doc/dev-guide-source/guides/events.md index 9e5f051b8446..735ceaefd6a5 100644 --- a/addon-sdk/source/doc/dev-guide-source/guides/events.md +++ b/addon-sdk/source/doc/dev-guide-source/guides/events.md @@ -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 are specific to an event type and are documented with the event emitter. -For example, the following add-on registers two listeners with the -[`private-browsing`](modules/sdk/private-browsing.html) module to -listen for the `start` and `stop` events, and logs a string to the console -reporting the change: +For example, the following add-on registers a listener with the +[`tabs`](modules/sdk/tabs.html) module to +listen for the `ready` event, and logs a string to the console +reporting the event: - var pb = require("sdk/private-browsing"); + var tabs = require("sdk/tabs"); - pb.on("start", function() { - console.log("Private browsing is on"); - }); - - pb.on("stop", function() { - console.log("Private browsing is off"); + tabs.on("ready", function () { + console.log("tab loaded"); }); It is not possible to enumerate the set of listeners for a given event. @@ -73,9 +69,8 @@ the event. ### Adding Listeners in Constructors ### -Event emitters may be modules, as is the case for the -`private-browsing` events, or they may be objects returned by -constructors. +Event emitters may be modules, as is the case for the `ready` event +above, or they may be objects returned by constructors. In the latter case the `options` object passed to the constructor typically 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 described above. -In the following add-on, we add two listeners to private-browsing's `start` -event, enter and exit private browsing, then remove the first listener and -enter private browsing again. +In the following add-on, we add two listeners to the +[`tabs` module's `ready` event](modules/sdk/tabs.html#ready). +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() { console.log("Listener 1"); - pb.removeListener("start", listener1); + tabs.removeListener("ready", listener1); } function listener2() { console.log("Listener 2"); } - pb.on("start", listener1); - pb.on("start", listener2); + tabs.on("ready", listener1); + tabs.on("ready", listener2); - pb.activate(); - pb.deactivate(); - pb.activate(); + tabs.open("https://www.mozilla.org"); + tabs.open("https://www.mozilla.org"); -Removing listeners is optional since they will be removed in any case -when the application or add-on is unloaded. +We should see output like this: + +
+info: tabevents: Listener 1
+info: tabevents: Listener 2
+info: tabevents: Listener 2
+
+ +Listeners will be removed automatically when the add-on is unloaded. diff --git a/addon-sdk/source/doc/dev-guide-source/package-spec.md b/addon-sdk/source/doc/dev-guide-source/package-spec.md index 039578ef5e0d..d99cef998b66 100644 --- a/addon-sdk/source/doc/dev-guide-source/package-spec.md +++ b/addon-sdk/source/doc/dev-guide-source/package-spec.md @@ -2,124 +2,221 @@ - 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 Specification # +# package.json # -A *package* is a directory that, at minimum, contains a JSON file -called `package.json`. This file is also referred to as the -*package manifest*. +The "package.json" file contains metadata for your add-on. -## 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"): + +
+{
+  "name": "my-addon",
+  "fullName": "my-addon",
+  "description": "a basic add-on",
+  "author": "",
+  "license": "MPL 2.0",
+  "version": "0.1"
+}
+
`package.json` may contain the following keys: -* `name` - the name of the package. The package system will only load - 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 - the package is ever built as an XPI, this is used as the add-on's - `em:name` element in its `install.rdf`. ++ + + -* `description` - a String describing the package. If the package is - ever built as an XPI, this is used as the add-on's - `em:description` element in its `install.rdf`. + + + + -* `author` - the original author of the package. The author may be a - String including an optional URL in parentheses and optional email - address in angle brackets. If the package is ever built as an XPI, - this is used as the add-on's `em:creator` element in its - `install.rdf`. + + + + -* `translators` - an Array of Strings consisted of translators of the package. + + + + -* `contributors` - may be an Array of additional author Strings. + + + + -* `homepage` - the URL of the package's website. + + + + -* `icon` - the relative path from the root of the package to a - PNG file containing the icon for the package. By default, this - is `icon.png`. If the package is built as an XPI, this is used - as the add-on's icon to display in the Add-on Manager's add-ons list. - This key maps on to the - [`iconURL` entry in the Install Manifest](https://developer.mozilla.org/en/install_manifests#iconURL), - so the icon may be up to 48x48 pixels in size. + + + + -* `icon64` - the relative path from the root of the package to a - PNG file containing the icon64 for the package. By default, this - is `icon64.png`. If the package is built as an XPI, this is used - as the add-on's icon to display in the Addon Manager's add-on details view. - This key maps on to the - [`icon64URL` entry in the Install Manifest](https://developer.mozilla.org/en/install_manifests#icon64URL), - so the icon should be 64x64 pixels in size. + + + + -* `preferences` - *experimental* - An array of JSON objects that use the following keys `name`, `type`, `value`, - `title`, and `description`. These JSON objects will be used to automatically - create a preferences interface for the addon in the Add-ons Manager. - For more information see the documentation of [simple-prefs](modules/sdk/simple-prefs.html). + + + + -* `license` - the name of the license as a String, with an optional - URL in parentheses. + + + + -* `id` - a globally unique identifier for the package. When the package is - built as an XPI, this is used as the add-on's `em:id` element in its - `install.rdf`. See the - [Program ID page](dev-guide/guides/program-id.html). + + + + -* `version` - a String representing the version of the package. If the - package is ever built as an XPI, this is used as the add-on's - `em:version` element in its `install.rdf`. + + + + -* `dependencies` - a String or Array of Strings representing package - names that this package requires in order to function properly. + + + + -* `lib` - a String representing the top-level module directory provided in - this package. Defaults to `"lib"`. - -* `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 + + + + -* `harnessClassID` - a String in the GUID format: - `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, where `x` represents a single - hexadecimal digit. It is used as a `classID` (CID) of the "harness service" - XPCOM component. Defaults to a random GUID generated by `cfx`. + + + + -* `permissions` - a set of permissions that the add-on needs. - * `private-browsing` - A Boolean indicating whether or not the - package supports private browsing. If this value is not `true` - then the package will not see private windows. + + + + -## Documentation ## + + + + -A package may optionally contain a -[Markdown](http://daringfireball.net/projects/markdown/)-formatted file -called `README.md` in its root directory. Package-browsing tools may display -this file to developers. + + + + -Additionally, Markdown files can be placed in an optional `docs` -directory. When package-browsing tools are asked to show the -documentation for a module, they will look in this directory for a -`.md` file with the module's name. Thus, for instance, if a user -browses to a module at `lib/foo/bar.js`, the package-browsing tool -will look for a file at `docs/foo/bar.js` to represent the module's -API documentation. + + + + -## Data Resources ## + + + + -Packages may optionally contain a directory called `data` into which -arbitrary files may be placed, such as images or text files. The -URL for these resources may be reached using the -[self](modules/sdk/self.html) module. + + + + + +
author

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.

+

This value will be used as the add-on's + em:creator + element in its "install.rdf".

contributors

An array of additional author + strings.

+

These values will be used as the add-on's + em:contributor + elements in its "install.rdf".

dependencies

String or array of strings representing package + names that this add-on requires in order to function properly.

description

The add-on's description. This defaults to the text + "a basic add-on".

+

This value will be used as the add-on's + em:description + element in its "install.rdf".

fullName

The full name of the package. It can contain spaces.

+ If this key is present its value will be used as the add-on's + em:name + element in its "install.rdf".

harnessClassID

String in the GUID format.

+

This is used as a + classID + of the "harness service" XPCOM component. Defaults to a random GUID generated by cfx.

homepage

The URL of the add-on's website.

+

This value will be used as the add-on's + em:homepageURL + element in its "install.rdf".

icon

The relative path from the root of the add-on to a + PNG file containing the icon for the add-on. Defaults to + "icon.png".

+

This value will be used as the add-on's + em:iconURL + element in its "install.rdf".

+

The icon may be up to 48x48 pixels in size.

icon64

The relative path from the root of the add-on to a + PNG file containing the large icon for the add-on. Defaults to + "icon64.png".

+

This value will be used as the add-on's + em:icon64URL + element in its "install.rdf".

+

The icon may be up to 64x64 pixels in size.

id

A globally unique identifier for the add-on.

+

This value will be used as the add-on's + em:id + element in its "install.rdf".

+

See the Program ID documentation.

lib

String representing the top-level module directory provided in + this add-on. Defaults to "lib".

license

The name of the license under which the add-on is distributed, with an optional + URL in parentheses. Defaults to "MPL 2.0".

main

String representing the name of a program module that is located in one of the top-level module directories specified by - `lib`. Defaults to `"main"`. + lib. + Defaults to "main".

name

The add-on's name. This name cannot contain spaces or periods, and + defaults to the name of the parent directory.

When the add-on is + built as an XPI, if the fullName + key is not present, name is used as the add-on's + em:name + element in its "install.rdf".

packages

String or array of strings representing paths to + directories containing additional packages. Defaults to + "packages".

permissions

A set of permissions that the add-on needs.

+

private-browsing: a boolean + indicating whether or not the + add-on supports private browsing. If this value is not true + 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 +private-browsing module.

+
preferences

An array of JSON objects that use the following keys: + name,type, value, + title, and description. These JSON objects will be used to + create a preferences interface for the add-on in the Add-ons Manager.

+

See the documentation for the + simple-prefs module.

tests

String representing the top-level module directory containing + test suites for this package. Defaults to "tests".

translators

An array of strings listing translators of this add-on.

+

These values will be used as the add-on's + em:translator + elements in its "install.rdf".

version

String representing the version of the add-on. Defaults to + "0.1".

+

This value will be used as the add-on's + em:version + element in its "install.rdf".

- [Markdown]: http://daringfireball.net/projects/markdown/ - [non-bootstrapped XUL extension]: #guide/xul-extensions diff --git a/addon-sdk/source/doc/dev-guide-source/tutorials/annotator/overview.md b/addon-sdk/source/doc/dev-guide-source/tutorials/annotator/overview.md index 897afc9bf183..bbdba2c802d4 100644 --- a/addon-sdk/source/doc/dev-guide-source/tutorials/annotator/overview.md +++ b/addon-sdk/source/doc/dev-guide-source/tutorials/annotator/overview.md @@ -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. 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 -`private-browsing` module for that. +the user creating annotations when in private browsing mode. The simplest way +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 ## diff --git a/addon-sdk/source/doc/dev-guide-source/tutorials/annotator/storing.md b/addon-sdk/source/doc/dev-guide-source/tutorials/annotator/storing.md index 823968b7447f..58fa4dc41f81 100644 --- a/addon-sdk/source/doc/dev-guide-source/tutorials/annotator/storing.md +++ b/addon-sdk/source/doc/dev-guide-source/tutorials/annotator/storing.md @@ -277,93 +277,20 @@ under quota.) ## Respecting Private Browsing ## -Since annotations record the user's browsing history we should prevent the user -from creating annotations while the browser is in -[Private Browsing](http://support.mozilla.com/en-US/kb/Private%20Browsing) mode. +Since annotations record the user's browsing history we should avoid recording +annotations in private windows. -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 -user can enter annotations. But we don't want to use that here, because we want -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()); - }); - } -
- - function toggleActivation() { - annotatorIsOn = !annotatorIsOn; - activateSelectors(); - return canEnterAnnotations(); - } -
- - 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. +Try it: execute cfx run and open a new private window: you should no longer +see the annotator's widget. Now we can create and store annotations, the last piece is to -[display them when the user loads the -page](dev-guide/tutorials/annotator/displaying.html). +[display them when the user loads the page](dev-guide/tutorials/annotator/displaying.html). \ No newline at end of file diff --git a/addon-sdk/source/doc/dev-guide-source/tutorials/event-targets.md b/addon-sdk/source/doc/dev-guide-source/tutorials/event-targets.md index 9ab275aa2b60..238a717426fa 100644 --- a/addon-sdk/source/doc/dev-guide-source/tutorials/event-targets.md +++ b/addon-sdk/source/doc/dev-guide-source/tutorials/event-targets.md @@ -10,7 +10,7 @@ incompatible changes to them in future releases. 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 -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). With the SDK, it's also simple to implement your own event targets. diff --git a/addon-sdk/source/doc/module-source/sdk/context-menu.md b/addon-sdk/source/doc/module-source/sdk/context-menu.md index 0d9e9daed18b..7d17657fab48 100644 --- a/addon-sdk/source/doc/module-source/sdk/context-menu.md +++ b/addon-sdk/source/doc/module-source/sdk/context-menu.md @@ -5,12 +5,6 @@ -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 page's context menu. You can add items that perform an action when clicked, 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 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 ------------- diff --git a/addon-sdk/source/doc/module-source/sdk/page-mod.md b/addon-sdk/source/doc/module-source/sdk/page-mod.md index 92eed7b47fb3..6d16e2168fff 100644 --- a/addon-sdk/source/doc/module-source/sdk/page-mod.md +++ b/addon-sdk/source/doc/module-source/sdk/page-mod.md @@ -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). + @class A page-mod object. Once created a page-mod will execute the supplied content diff --git a/addon-sdk/source/doc/module-source/sdk/panel.md b/addon-sdk/source/doc/module-source/sdk/panel.md index c6941c75e073..0c4cdf98a8d9 100644 --- a/addon-sdk/source/doc/module-source/sdk/panel.md +++ b/addon-sdk/source/doc/module-source/sdk/panel.md @@ -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 listening to its `show` and `hide` events. +Opening a panel will close an already opened panel. + +
+If your add-on has +opted 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. +
+ ## Panel Content ## 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, 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. + @class 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. @prop [height] {number} 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} The URL of the content to load in the panel. @prop [allow] {object} @@ -472,6 +497,12 @@ The height of the panel in pixels. The width of the panel in pixels. + +@property {boolean} +Whether of not focus will be taken away when the panel is shown. +This property is read-only. + + @property {string} The URL of content loaded into the panel. This can point to diff --git a/addon-sdk/source/doc/module-source/sdk/private-browsing.md b/addon-sdk/source/doc/module-source/sdk/private-browsing.md index b0020247a2fe..70363112d14d 100644 --- a/addon-sdk/source/doc/module-source/sdk/private-browsing.md +++ b/addon-sdk/source/doc/module-source/sdk/private-browsing.md @@ -6,90 +6,170 @@ -The `private-browsing` module allows you to access Firefox's private browsing -mode, detecting if it is active and when its state changes. +## Per-window private browsing ## -This module is available in all applications. However, only Firefox will ever -transition into or out of private browsing mode. For all other applications, -`pb.isActive` will always be `false`, and none of the events will be emitted. +Firefox 20 introduces per-window private browsing. This means that private +browsing status is a property of an individual browser window. -
-The activate -and deactivate -functions, isActive -property, start, -and stop -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. -
+The user enters +private browsing by opening a new private browser window. When they do this, +any existing non-private windows are kept open, so the user will typically +have both private and non-private windows open at the same time. - -@property {boolean} - This read-only boolean is `true` if global private browsing mode is turned on. +## Opting into private browsing ## -
- This property is deprecated. It will continue to work until version 1.13 of the SDK. - From version 1.13 onwards it will always return `false`. -
-
+Add-ons built using the SDK must opt into private browsing by setting the +following key in their [`package.json`](dev-guide/package-spec.html) file: + +
"permissions": {"private-browsing": true}
+ +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. @function - Returns `true` if the argument is a private window or tab. -@param [thing] {any} - The thing to check if it is private, only handles windows and tabs at the moment. - Everything else returns `false` automatically. - In global private browsing mode, this method returns the same value as `isActive`. - - - -@function - Turns on global private browsing mode. - -
- 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. -
-
- - -@function - Turns off global private browsing mode. - -
- 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. -
-
- - -@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. - }); - -
- 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. -
-
- - -@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. - }); - -
- 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. -
+Function to check whether the given object is private. It takes an +object as an argument, and returns `true` only if the object is: + +* a private [`BrowserWindow`](modules/sdk/windows.html#BrowserWindow) or +* a [`tab`](modules/sdk/tabs.html#Tab) belonging to a private window, or +* a [`worker`](modules/sdk/content/worker.html#Worker) that's associated +with a document hosted in a private window + +@param [object] {any} + The object to check. This may be a +[`BrowserWindow`](modules/sdk/windows.html#BrowserWindow), +[`tab`](modules/sdk/tabs.html#Tab), or +[`worker`](modules/sdk/content/worker.html#Worker).
diff --git a/addon-sdk/source/doc/module-source/sdk/selection.md b/addon-sdk/source/doc/module-source/sdk/selection.md index 54955dabd649..aa4b4546fa64 100644 --- a/addon-sdk/source/doc/module-source/sdk/selection.md +++ b/addon-sdk/source/doc/module-source/sdk/selection.md @@ -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`, `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 -------- diff --git a/addon-sdk/source/doc/module-source/sdk/self.md b/addon-sdk/source/doc/module-source/sdk/self.md index 6bb2fec7194b..23c69ce36fc7 100644 --- a/addon-sdk/source/doc/module-source/sdk/self.md +++ b/addon-sdk/source/doc/module-source/sdk/self.md @@ -53,8 +53,9 @@ downgrade @property {boolean} -This property indicates add-on's support for private browsing. It comes from the -`private-browsing` property set in the `package.json` file in the main package. +This property indicates whether or not the add-on supports private browsing. +It comes from the [`private-browsing` key](dev-guide/package-spec.html#permissions) +in the add-on's `package.json` file. diff --git a/addon-sdk/source/doc/module-source/sdk/simple-storage.md b/addon-sdk/source/doc/module-source/sdk/simple-storage.md index f65c2eb9f09b..7fd54df06012 100644 --- a/addon-sdk/source/doc/module-source/sdk/simple-storage.md +++ b/addon-sdk/source/doc/module-source/sdk/simple-storage.md @@ -172,33 +172,13 @@ data you remove is up to you. For example: Private Browsing ---------------- 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]. -While private browsing mode is active, you should not store any sensitive data. - -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 +other sensitive data, your add-on should respect +[private browsing](http://support.mozilla.com/en-US/kb/Private+Browsing). +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). @property {object} diff --git a/addon-sdk/source/doc/module-source/sdk/system/events.md b/addon-sdk/source/doc/module-source/sdk/system/events.md index 2d00038c5c7f..25075267f4d4 100644 --- a/addon-sdk/source/doc/module-source/sdk/system/events.md +++ b/addon-sdk/source/doc/module-source/sdk/system/events.md @@ -13,10 +13,12 @@ You can find a list of events dispatched by firefox codebase var events = require("sdk/system/events"); var { Ci } = require("chrome"); - events.on("http-on-modify-request", function (event) { + function listener(event) { var channel = event.subject.QueryInterface(Ci.nsIHttpChannel); channel.setRequestHeader("User-Agent", "MyBrowser/1.0", false); - }); + } + + events.on("http-on-modify-request", listener); @function diff --git a/addon-sdk/source/doc/module-source/sdk/tabs.md b/addon-sdk/source/doc/module-source/sdk/tabs.md index ba65f925d9b6..0cce2280c224 100644 --- a/addon-sdk/source/doc/module-source/sdk/tabs.md +++ b/addon-sdk/source/doc/module-source/sdk/tabs.md @@ -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). + @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. @prop isPrivate {boolean} -Boolean which will determine if a private tab should be opened. -Private browsing mode must be supported in order to do this. +Boolean which will determine whether the new tab should be private or not. +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. @prop [isPinned] {boolean} diff --git a/addon-sdk/source/doc/module-source/sdk/tabs/utils.md b/addon-sdk/source/doc/module-source/sdk/tabs/utils.md index 25396d7fcfe7..10b282f45cc6 100644 --- a/addon-sdk/source/doc/module-source/sdk/tabs/utils.md +++ b/addon-sdk/source/doc/module-source/sdk/tabs/utils.md @@ -39,8 +39,17 @@ A browser window. @function -Returns the tabs for the specified `window`, or the tabs -across all the browser's windows if `window` is omitted. +Returns the tabs for the specified `window`. + +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} Optional. @returns {Array} diff --git a/addon-sdk/source/doc/module-source/sdk/widget.md b/addon-sdk/source/doc/module-source/sdk/widget.md index 9d023b6335e3..813a6575dc2f 100644 --- a/addon-sdk/source/doc/module-source/sdk/widget.md +++ b/addon-sdk/source/doc/module-source/sdk/widget.md @@ -346,6 +346,15 @@ listener, the panel will not be anchored: 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 ## For conciseness, these examples create their content scripts as strings and use @@ -685,6 +694,9 @@ The related `WidgetView` object. @event 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. diff --git a/addon-sdk/source/doc/module-source/sdk/window/utils.md b/addon-sdk/source/doc/module-source/sdk/window/utils.md index 975bcfc9b232..d0def80b23ff 100644 --- a/addon-sdk/source/doc/module-source/sdk/window/utils.md +++ b/addon-sdk/source/doc/module-source/sdk/window/utils.md @@ -5,6 +5,21 @@ The `window/utils` module provides helper functions for working with 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). + @function Get the topmost browser window, as an @@ -169,6 +184,11 @@ element. @function Returns an array of all currently opened windows. 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} Array of `nsIDOMWindow` instances. diff --git a/addon-sdk/source/doc/module-source/sdk/windows.md b/addon-sdk/source/doc/module-source/sdk/windows.md index 653b8d642d5a..0f1ab6c089e6 100644 --- a/addon-sdk/source/doc/module-source/sdk/windows.md +++ b/addon-sdk/source/doc/module-source/sdk/windows.md @@ -4,21 +4,29 @@ +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 -tabs, and open/close related functions and events. +* [enumerate the currently opened browser windows](modules/sdk/windows.html#browserWindows) +* [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 -access to non-browser windows such as the Bookmarks Library, preferences -or other non-browser windows created via add-ons. +## Private Windows ## + +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). @property {List} -An object that contains various properties and methods to access -functionality from browser windows, such as opening new windows, accessing -their tabs or switching the current active window. - -`browserWindows` provides access to all the currently open browser windows: +`browserWindows` provides access to all the currently open browser windows as +[BrowserWindow](modules/sdk/windows.html#BrowserWindow) objects. var windows = require("sdk/windows"); for each (var window in windows.browserWindows) { @@ -27,9 +35,7 @@ their tabs or switching the current active window. console.log(windows.browserWindows.length); -Object emits all the events listed under "Events" section. - -####Examples#### +This object emits all the events listed under the "Events" section: var windows = require("sdk/windows").browserWindows; @@ -119,8 +125,8 @@ String URL to be opened in the new window. This is a required property. @prop isPrivate {boolean} -Boolean which will determine if a private window should be opened. -Private browsing mode must be supported in order to do this. +Boolean which will determine whether the new window should be private or not. +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. @prop [onOpen] {function} @@ -185,8 +191,7 @@ Returns `true` if the window is in private browsing mode, and `false` otherwise.
This property is deprecated. - From version 1.14, please consider using following code instead:
- require("private-browsing").isPrivate(browserWindow) + From version 1.14, use the private-browsing module's isPrivate() function instead.
diff --git a/addon-sdk/source/examples/annotator/lib/main.js b/addon-sdk/source/examples/annotator/lib/main.js index a21320af0592..5c33241e983c 100644 --- a/addon-sdk/source/examples/annotator/lib/main.js +++ b/addon-sdk/source/examples/annotator/lib/main.js @@ -8,7 +8,6 @@ var data = require('self').data; var panels = require('panel'); var simpleStorage = require('simple-storage'); var notifications = require("notifications"); -var privateBrowsing = require('private-browsing'); /* 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 */ @@ -66,21 +58,17 @@ Function to tell the selector page mod that the add-on has become (in)active function activateSelectors() { selectors.forEach( function (selector) { - selector.postMessage(canEnterAnnotations()); + selector.postMessage(annotatorIsOn); }); } /* Toggle activation: update the on/off state and notify the selectors. -Toggling activation is disabled when private browsing is on. */ function toggleActivation() { - if (privateBrowsing.isActive) { - return false; - } annotatorIsOn = !annotatorIsOn; activateSelectors(); - return canEnterAnnotations(); + return annotatorIsOn; } function detachWorker(worker, workerArray) { @@ -138,7 +126,7 @@ display it. contentScriptFile: [data.url('jquery-1.4.2.min.js'), data.url('selector.js')], onAttach: function(worker) { - worker.postMessage(canEnterAnnotations()); + worker.postMessage(annotatorIsOn); selectors.push(worker); worker.port.on('show', function(data) { annotationEditor.annotationAnchor = data; @@ -217,22 +205,6 @@ recent annotations until we are back in quota. 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 annotation to be displayed. diff --git a/addon-sdk/source/lib/sdk/content/content-worker.js b/addon-sdk/source/lib/sdk/content/content-worker.js index 8cf9c5c3c811..6c7d8fb69bb1 100644 --- a/addon-sdk/source/lib/sdk/content/content-worker.js +++ b/addon-sdk/source/lib/sdk/content/content-worker.js @@ -224,7 +224,7 @@ const ContentWorker = Object.freeze({ }); }, - injectMessageAPI: function injectMessageAPI(exports, pipe) { + injectMessageAPI: function injectMessageAPI(exports, pipe, console) { let { eventEmitter: port, emit : portEmit } = ContentWorker.createEventEmitter(pipe.emit.bind(null, "event")); @@ -293,7 +293,7 @@ const ContentWorker = Object.freeze({ ContentWorker.injectConsole(exports, pipe); ContentWorker.injectTimers(exports, chromeAPI, pipe, exports.console); - ContentWorker.injectMessageAPI(exports, pipe); + ContentWorker.injectMessageAPI(exports, pipe, exports.console); if ( options !== undefined ) { ContentWorker.injectOptions(exports, options); } diff --git a/addon-sdk/source/lib/sdk/content/worker.js b/addon-sdk/source/lib/sdk/content/worker.js index f4845b361c4c..bf8ccb25a615 100644 --- a/addon-sdk/source/lib/sdk/content/worker.js +++ b/addon-sdk/source/lib/sdk/content/worker.js @@ -378,6 +378,13 @@ const Worker = EventEmitter.compose({ on: 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 * 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. * @param {Number|String|JSON} data */ - postMessage: function postMessage(data) { - if (!this._contentWorker) - throw new Error(ERR_DESTROYED); - if (this._frozen) - throw new Error(ERR_FROZEN); - - this._contentWorker.emit("message", data); + postMessage: function (data) { + let args = ['message'].concat(Array.slice(arguments)); + if (!this._inited) { + this._earlyEvents.push(args); + return; + } + processMessage.apply(this, args); }, /** @@ -410,9 +417,8 @@ const Worker = EventEmitter.compose({ // before Worker.constructor gets called. (For ex: Panel) // create an event emitter that receive and send events from/to the worker - let self = this; this._port = EventEmitterTrait.create({ - emit: function () self._emitEventToContent(Array.slice(arguments)) + emit: this._emitEventToContent.bind(this) }); // expose wrapped port, that exposes only public properties: @@ -438,24 +444,13 @@ const Worker = EventEmitter.compose({ * Emit a custom event to the content script, * i.e. emit this event on `self.port` */ - _emitEventToContent: function _emitEventToContent(args) { - // We need to save events that are emitted before the worker is - // initialized + _emitEventToContent: function () { + let args = ['event'].concat(Array.slice(arguments)); if (!this._inited) { this._earlyEvents.push(args); return; } - - 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)); + processMessage.apply(this, args); }, // Is worker connected to the content worker sandbox ? @@ -465,13 +460,6 @@ const Worker = EventEmitter.compose({ // Content script should not be reachable if frozen. _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) { options = options || {}; @@ -525,9 +513,11 @@ const Worker = EventEmitter.compose({ this._inited = true; this._frozen = false; - // Flush all events that have been fired before the worker is initialized. - this._earlyEvents.forEach((function (args) this._emitEventToContent(args)). - bind(this)); + // Process all events and messages that were fired before the + // worker was initialized. + this._earlyEvents.forEach((function (args) { + processMessage.apply(this, args); + }).bind(this)); }, _documentUnload: function _documentUnload(subject, topic, data) { @@ -590,7 +580,7 @@ const Worker = EventEmitter.compose({ if (this._windowID) { this._windowID = null; observers.remove("inner-window-destroyed", this._documentUnload); - this._earlyEvents.slice(0, this._earlyEvents.length); + this._earlyEvents.length = 0; this._emit("detach"); } }, @@ -622,4 +612,20 @@ const Worker = EventEmitter.compose({ */ _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; diff --git a/addon-sdk/source/lib/sdk/deprecated/window-utils.js b/addon-sdk/source/lib/sdk/deprecated/window-utils.js index fc681035a918..554c1513d8e7 100644 --- a/addon-sdk/source/lib/sdk/deprecated/window-utils.js +++ b/addon-sdk/source/lib/sdk/deprecated/window-utils.js @@ -15,7 +15,7 @@ const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser, getMostRecentBrowserWindow, getMostRecentWindow } = require('../window/utils'); const errors = require('../deprecated/errors'); 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 windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1']. @@ -24,7 +24,7 @@ const appShellService = Cc['@mozilla.org/appshell/appShellService;1']. getService(Ci.nsIAppShellService); // 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. diff --git a/addon-sdk/source/lib/sdk/event/core.js b/addon-sdk/source/lib/sdk/event/core.js index e63e3b98dcc3..c4fa868146a9 100644 --- a/addon-sdk/source/lib/sdk/event/core.js +++ b/addon-sdk/source/lib/sdk/event/core.js @@ -94,20 +94,23 @@ function emit(target, type, message /*, ...*/) { * arguments. */ emit.lazy = function lazy(target, type, message /*, ...*/) { - let args = Array.slice(arguments, 2) - let listeners = observers(target, type).slice() - while (listeners.length) { - try { - yield listeners.shift().apply(target, args); - } + let args = Array.slice(arguments, 2); + let listeners = observers(target, type).slice(); + let index = 0; + let count = listeners.length; + + // 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) { // If exception is not thrown by a error listener and error listener is // registered emit `error` event. Otherwise dump exception to the console. - if (type !== 'error' && observers(target, 'error').length) - emit(target, 'error', error); - else - console.exception(error); + if (type !== 'error') emit(target, 'error', error); + else console.exception(error); } + index = index + 1; } } exports.emit = emit; diff --git a/addon-sdk/source/lib/sdk/frame/hidden-frame.js b/addon-sdk/source/lib/sdk/frame/hidden-frame.js index dec5dc8853df..99e7e9cbe2b3 100644 --- a/addon-sdk/source/lib/sdk/frame/hidden-frame.js +++ b/addon-sdk/source/lib/sdk/frame/hidden-frame.js @@ -21,10 +21,11 @@ const { defer } = require("../core/promise"); const { when: unload } = require("../system/unload"); const { validateOptions, getTypeOf } = require("../deprecated/api-utils"); const { window } = require("../addon/window"); +const { fromIterator } = require("../util/array"); // This cache is used to access friend properties between functions // without exposing them on the public API. -let cache = []; +let cache = new Set(); let elements = new WeakMap(); function contentLoaded(target) { @@ -75,20 +76,13 @@ var HiddenFrame = Class({ }); 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) { if (!(frame instanceof HiddenFrame)) throw Error("The object to be added must be a HiddenFrame."); // This instance was already added. - if (isFrameCached(frame)) return frame; - else cache.push(frame); + if (cache.has(frame)) return frame; + else cache.add(frame); let element = makeFrame(window.document, { nodeName: "iframe", @@ -111,14 +105,14 @@ function removeHiddenFrame(frame) { if (!(frame instanceof 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 - cache.splice(cache.indexOf(frame), 1); + cache.delete(frame); emit(frame, "unload") let element = frame.element if (element) element.parentNode.removeChild(element) } exports.remove = removeHiddenFrame; -unload(function() cache.splice(0).forEach(removeHiddenFrame)); +unload(function() fromIterator(cache).forEach(removeHiddenFrame)); diff --git a/addon-sdk/source/lib/sdk/panel.js b/addon-sdk/source/lib/sdk/panel.js index 25bf8bb1556c..c6081166786b 100644 --- a/addon-sdk/source/lib/sdk/panel.js +++ b/addon-sdk/source/lib/sdk/panel.js @@ -17,16 +17,20 @@ const { validateOptions: valid } = require('./deprecated/api-utils'); const { Symbiont } = require('./content/content'); const { EventEmitter } = require('./deprecated/events'); const { setTimeout } = require('./timers'); +const { on, off, emit } = require('./system/events'); const runtime = require('./system/runtime'); const { getDocShell } = require("./frame/utils"); const { getWindow } = require('./panel/window'); const { isPrivateBrowsingSupported } = require('./self'); const { isWindowPBSupported } = require('./private-browsing/utils'); +const { getNodeView } = require('./view/core'); const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", ON_SHOW = 'popupshown', 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) { 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) { this._onShow = this._onShow.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('propertyChange', this._onChange.bind(this)); @@ -82,6 +89,12 @@ const Panel = Symbiont.resolve({ this.height = options.height; if ('contentURL' in options) 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); }, @@ -91,6 +104,7 @@ const Panel = Symbiont.resolve({ this._removeAllListeners('hide'); this._removeAllListeners('propertyChange'); this._removeAllListeners('inited'); + off('sdk-panel-show', this._onAnyPanelShow); // defer cleanup to be performed after panel gets hidden this._xulPanel = null; this._symbiontDestructor(this); @@ -109,13 +123,16 @@ const Panel = Symbiont.resolve({ set height(value) this._height = valid({ $: value }, { $: validNumber }).$ || this._height, _height: 240, + /* Public API: Panel.focus */ + get focus() this._focus, + _focus: true, /* Public API: Panel.isShowing */ get isShowing() !!this._xulPanel && this._xulPanel.state == "open", /* Public API: Panel.show */ show: function show(anchor) { - anchor = anchor || null; + anchor = anchor ? getNodeView(anchor) : null; let anchorWindow = getWindow(anchor); // 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 xulPanel = this._xulPanel; + let panel = this; if (!xulPanel) { xulPanel = this._xulPanel = document.createElementNS(XUL_NS, 'panel'); xulPanel.setAttribute("type", "arrow"); @@ -165,7 +183,7 @@ const Panel = Symbiont.resolve({ xulPanel.appendChild(frame); document.getElementById("mainPopupSet").appendChild(xulPanel); } - let { width, height } = this, x, y, position; + let { width, height, focus } = this, x, y, position; if (!anchor) { // 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.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 function waitForBinding() { if (!xulPanel.openPopup) { setTimeout(waitForBinding, 50); return; } - xulPanel.openPopup(anchor, position, x, y); + + if (xulPanel.state !== 'hiding') { + xulPanel.openPopup(anchor, position, x, y); + } } waitForBinding(); @@ -289,6 +319,8 @@ const Panel = Symbiont.resolve({ * text color. */ _applyStyleToDocument: function _applyStyleToDocument() { + if (this._defaultStyleApplied) + return; try { let win = this._xulPanel.ownerDocument.defaultView; let node = win.document.getAnonymousElementByAttribute( @@ -309,6 +341,7 @@ const Panel = Symbiont.resolve({ container.insertBefore(style, container.firstChild); else container.appendChild(style); + this._defaultStyleApplied = true; } catch(e) { console.error("Unable to apply panel style"); @@ -333,6 +366,16 @@ const Panel = Symbiont.resolve({ 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. */ diff --git a/addon-sdk/source/lib/sdk/panel/window.js b/addon-sdk/source/lib/sdk/panel/window.js index f7d62261a459..a69c2eea72b1 100644 --- a/addon-sdk/source/lib/sdk/panel/window.js +++ b/addon-sdk/source/lib/sdk/panel/window.js @@ -6,11 +6,12 @@ const { getMostRecentBrowserWindow, windows: getWindows } = require('../window/utils'); const { ignoreWindow } = require('../private-browsing/utils'); const { isPrivateBrowsingSupported } = require('../self'); +const { isGlobalPBSupported } = require('../private-browsing/utils'); function getWindow(anchor) { let window; let windows = getWindows("navigator:browser", { - includePrivate: isPrivateBrowsingSupported + includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported }); if (anchor) { diff --git a/addon-sdk/source/lib/sdk/private-browsing/utils.js b/addon-sdk/source/lib/sdk/private-browsing/utils.js index 2e4253c7d075..5653f8401cc8 100644 --- a/addon-sdk/source/lib/sdk/private-browsing/utils.js +++ b/addon-sdk/source/lib/sdk/private-browsing/utils.js @@ -54,7 +54,7 @@ let isTabPBSupported = exports.isTabPBSupported = !pbService && !!PrivateBrowsingUtils && is('Fennec') && satisfiesVersion(version, '>=20.0*'); function ignoreWindow(window) { - return !isPrivateBrowsingSupported && isWindowPrivate(window); + return !isPrivateBrowsingSupported && isWindowPrivate(window) && !isGlobalPBSupported; } exports.ignoreWindow = ignoreWindow; diff --git a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js index 5fe3c662089f..145f83ff03ea 100644 --- a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js +++ b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js @@ -7,7 +7,7 @@ const { Cc, Ci } = require('chrome'); const { Class } = require('../core/heritage'); const { tabNS, rawTabNS } = require('./namespace'); const { EventTarget } = require('../event/target'); -const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL, getContentWindowForTab, +const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL, getTabContentWindow, getTabForBrowser, setTabURL, getOwnerWindow, getTabContentType, getTabId } = require('./utils'); const { emit } = require('../event/core'); @@ -129,7 +129,7 @@ const Tab = Class({ // BUG 792946 https://bugzilla.mozilla.org/show_bug.cgi?id=792946 // TODO: fix this circular dependency 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) { - return getContentWindowForTab(tabNS(tab).tab); + return getTabContentWindow(tabNS(tab).tab); }); diff --git a/addon-sdk/source/lib/sdk/tabs/utils.js b/addon-sdk/source/lib/sdk/tabs/utils.js index cc49bf229514..b6c6860f0080 100644 --- a/addon-sdk/source/lib/sdk/tabs/utils.js +++ b/addon-sdk/source/lib/sdk/tabs/utils.js @@ -16,9 +16,10 @@ const { Ci } = require('chrome'); const { defer } = require("../lang/functional"); const { windows, isBrowser } = require('../window/utils'); const { isPrivateBrowsingSupported } = require('../self'); +const { isGlobalPBSupported } = require('../private-browsing/utils'); // 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) { let gBrowser = getTabBrowserForTab(tab); @@ -166,12 +167,6 @@ function getBrowserForTab(tab) { } exports.getBrowserForTab = getBrowserForTab; - -function getContentWindowForTab(tab) { - return getBrowserForTab(tab).contentWindow; -} -exports.getContentWindowForTab = getContentWindowForTab; - function getTabId(tab) { if (tab.browser) // fennec return tab.id diff --git a/addon-sdk/source/lib/sdk/test/loader.js b/addon-sdk/source/lib/sdk/test/loader.js index 74c32118d5fe..9efbf71d59e2 100644 --- a/addon-sdk/source/lib/sdk/test/loader.js +++ b/addon-sdk/source/lib/sdk/test/loader.js @@ -51,7 +51,11 @@ exports.LoaderWithHookedConsole = function (module, callback) { warn: hook.bind("warn"), error: hook.bind("error"), 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 diff --git a/addon-sdk/source/lib/sdk/util/array.js b/addon-sdk/source/lib/sdk/util/array.js index 2464f07ed686..539c2166ba43 100644 --- a/addon-sdk/source/lib/sdk/util/array.js +++ b/addon-sdk/source/lib/sdk/util/array.js @@ -90,8 +90,14 @@ exports.flatten = function flatten(array){ function fromIterator(iterator) { let array = []; - for each (let item in iterator) - array.push(item); + if (iterator.__iterator__) { + for each (let item in iterator) + array.push(item); + } + else { + for (let item of iterator) + array.push(item); + } return array; } exports.fromIterator = fromIterator; diff --git a/addon-sdk/source/lib/sdk/view/core.js b/addon-sdk/source/lib/sdk/view/core.js new file mode 100644 index 000000000000..f53c70293fb5 --- /dev/null +++ b/addon-sdk/source/lib/sdk/view/core.js @@ -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; diff --git a/addon-sdk/source/lib/sdk/widget.js b/addon-sdk/source/lib/sdk/widget.js index 56daf8fea526..e872f45ac756 100644 --- a/addon-sdk/source/lib/sdk/widget.js +++ b/addon-sdk/source/lib/sdk/widget.js @@ -48,6 +48,7 @@ const { isBrowser } = require("./window/utils"); const { setTimeout } = require("./timers"); const unload = require("./system/unload"); const { uuid } = require("./util/uuid"); +const { getNodeView } = require("./view/core"); // Data types definition const valid = { @@ -362,7 +363,6 @@ const Widget = function Widget(options) { exports.Widget = Widget; - /** * 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 // handler, but it does have a panel, display the 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) { @@ -472,6 +475,7 @@ const WidgetViewTrait = LightTrait.compose(EventEmitterTrait, LightTrait({ })); + const WidgetView = function WidgetView(baseWidget) { let w = WidgetViewTrait.create(WidgetView.prototype); w._initWidgetView(baseWidget); @@ -479,7 +483,6 @@ const WidgetView = function WidgetView(baseWidget) { } - /** * Keeps track of all browser windows. * Exposes methods for adding/removing widgets diff --git a/addon-sdk/source/lib/sdk/window/browser.js b/addon-sdk/source/lib/sdk/window/browser.js index d4258e931d5e..9f59f8fa2d2c 100644 --- a/addon-sdk/source/lib/sdk/window/browser.js +++ b/addon-sdk/source/lib/sdk/window/browser.js @@ -9,7 +9,7 @@ const { on, off, once } = require('../event/core'); const { method } = require('../lang/functional'); const { getWindowTitle } = require('./utils'); const unload = require('../system/unload'); -const { isWindowPrivate } = require('../private-browsing/utils'); +const { isWindowPrivate } = require('../window/utils'); const { EventTarget } = require('../event/target'); const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils'); const { deprecateUsage } = require('../util/deprecate'); diff --git a/addon-sdk/source/lib/sdk/windows/fennec.js b/addon-sdk/source/lib/sdk/windows/fennec.js index a497ea9f2fee..b5c826c52903 100644 --- a/addon-sdk/source/lib/sdk/windows/fennec.js +++ b/addon-sdk/source/lib/sdk/windows/fennec.js @@ -5,9 +5,8 @@ const { Class } = require('../core/heritage'); const { BrowserWindow } = require('../window/browser'); -const windowUtils = require('../deprecated/window-utils'); -const { WindowTracker } = windowUtils; -const { isBrowser } = require('../window/utils'); +const { WindowTracker } = require('../deprecated/window-utils'); +const { isBrowser, getMostRecentBrowserWindow } = require('../window/utils'); const { windowNS } = require('../window/namespace'); const { on, off, once, emit } = require('../event/core'); const { method } = require('../lang/functional'); @@ -25,7 +24,7 @@ let BrowserWindows = Class({ List.prototype.initialize.apply(this); }, get activeWindow() { - let window = windowUtils.activeBrowserWindow; + let window = getMostRecentBrowserWindow(); return window ? getBrowserWindow({window: window}) : null; }, open: function open(options) { diff --git a/addon-sdk/source/lib/sdk/windows/tabs-fennec.js b/addon-sdk/source/lib/sdk/windows/tabs-fennec.js index da6f5bc538ef..ba6793d274f8 100644 --- a/addon-sdk/source/lib/sdk/windows/tabs-fennec.js +++ b/addon-sdk/source/lib/sdk/windows/tabs-fennec.js @@ -8,7 +8,8 @@ const { Tab } = require('../tabs/tab'); const { browserWindows } = require('./fennec'); const { windowNS } = require('../window/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 { getTabForBrowser, getTabForRawTab } = require('../tabs/helpers'); const { on, once, off, emit } = require('../event/core'); @@ -18,8 +19,8 @@ const { EventTarget } = require('../event/target'); const { when: unload } = require('../system/unload'); const { windowIterator } = require('../deprecated/window-utils'); const { List, addListItem, removeListItem } = require('../util/list'); -const { isPrivateBrowsingSupported } = require('sdk/self'); -const { isTabPBSupported } = require('sdk/private-browsing/utils'); +const { isPrivateBrowsingSupported } = require('../self'); +const { isTabPBSupported, ignoreWindow } = require('../private-browsing/utils'); const mainWindow = windowNS(browserWindows.activeWindow).window; @@ -113,6 +114,10 @@ function removeTab(tab) { function onTabOpen(event) { let browser = event.target; + // Eventually ignore private tabs + if (ignoreWindow(browser.contentWindow)) + return; + let tab = getTabForBrowser(browser); if (tab === null) { let rawTab = getRawTabForBrowser(browser); @@ -132,8 +137,14 @@ function onTabOpen(event) { // TabSelect function onTabSelect(event) { + let browser = event.target; + + // Eventually ignore private tabs + if (ignoreWindow(browser.contentWindow)) + return; + // Set value whenever new tab becomes active. - let tab = getTabForBrowser(event.target); + let tab = getTabForBrowser(browser); emit(tab, 'activate', tab); emit(gTabs, 'activate', tab); diff --git a/addon-sdk/source/lib/sdk/windows/tabs-firefox.js b/addon-sdk/source/lib/sdk/windows/tabs-firefox.js index b5df62c89a94..22e469d4b20f 100644 --- a/addon-sdk/source/lib/sdk/windows/tabs-firefox.js +++ b/addon-sdk/source/lib/sdk/windows/tabs-firefox.js @@ -16,7 +16,7 @@ const { getOwnerWindow, getActiveTab, getTabs, openTab } = require("../tabs/utils"); const { Options } = require("../tabs/common"); const { observer: tabsObserver } = require("../tabs/observer"); -const { ignoreWindow, isWindowPrivate } = require("../private-browsing/utils"); +const { ignoreWindow } = require("../private-browsing/utils"); const TAB_BROWSER = "tabbrowser"; diff --git a/addon-sdk/source/python-lib/cuddlefish/prefs.py b/addon-sdk/source/python-lib/cuddlefish/prefs.py index b6c6b7f151d5..6c5863b20b21 100644 --- a/addon-sdk/source/python-lib/cuddlefish/prefs.py +++ b/addon-sdk/source/python-lib/cuddlefish/prefs.py @@ -11,6 +11,9 @@ DEFAULT_COMMON_PREFS = { 'javascript.options.strict': True, 'javascript.options.showInConsole': True, + # Allow remote connections to the debugger + 'devtools.debugger.remote-enabled' : True, + 'extensions.sdk.console.logLevel': 'info', 'extensions.checkCompatibility.nightly' : False, @@ -51,6 +54,7 @@ DEFAULT_FIREFOX_PREFS = { 'browser.startup.homepage' : 'about:blank', 'startup.homepage_welcome_url' : 'about:blank', 'devtools.errorconsole.enabled' : True, + 'devtools.chrome.enabled' : True, # Disable the feedback extension 'extensions.testpilot.runStudies' : False, diff --git a/addon-sdk/source/python-lib/cuddlefish/rdf.py b/addon-sdk/source/python-lib/cuddlefish/rdf.py index 6ce69f11de00..795a7da777fd 100644 --- a/addon-sdk/source/python-lib/cuddlefish/rdf.py +++ b/addon-sdk/source/python-lib/cuddlefish/rdf.py @@ -165,11 +165,11 @@ def gen_manifest(template_root_dir, target_cfg, jid, ta_desc.appendChild(elem) elem = dom.createElement("em:minVersion") - elem.appendChild(dom.createTextNode("18.0")) + elem.appendChild(dom.createTextNode("19.0")) ta_desc.appendChild(elem) elem = dom.createElement("em:maxVersion") - elem.appendChild(dom.createTextNode("21.0a1")) + elem.appendChild(dom.createTextNode("22.0a1")) ta_desc.appendChild(elem) if target_cfg.get("homepage"): diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py index bea50c300b32..ef5b2f9e6120 100644 --- a/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py @@ -141,16 +141,15 @@ class TestInit(unittest.TestCase): class TestCfxQuits(unittest.TestCase): - def run_cfx(self, addon_name, command): + def run_cfx(self, addon_path, command): old_cwd = os.getcwd() - addon_path = os.path.join(tests_path, - "addons", addon_name) os.chdir(addon_path) import sys old_stdout = sys.stdout old_stderr = sys.stderr sys.stdout = out = StringIO() sys.stderr = err = StringIO() + rc = 0 try: import cuddlefish args = list(command) @@ -182,17 +181,45 @@ class TestCfxQuits(unittest.TestCase): container) self.fail(standardMsg) - def test_run(self): - rc, out, err = self.run_cfx("simplest-test", ["run"]) + def test_cfx_run(self): + addon_path = os.path.join(tests_path, + "addons", "simplest-test") + rc, out, err = self.run_cfx(addon_path, ["run"]) self.assertEqual(rc, 0) self.assertIn("Program terminated successfully.", err) - def test_test(self): - rc, out, err = self.run_cfx("simplest-test", ["test"]) + def test_cfx_test(self): + addon_path = os.path.join(tests_path, + "addons", "simplest-test") + rc, out, err = self.run_cfx(addon_path, ["test"]) self.assertEqual(rc, 0) self.assertIn("1 of 1 tests passed.", 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__": unittest.main() diff --git a/addon-sdk/source/test/addons/layout-change/main.js b/addon-sdk/source/test/addons/layout-change/main.js index c031afdf7286..0de501201ab4 100644 --- a/addon-sdk/source/test/addons/layout-change/main.js +++ b/addon-sdk/source/test/addons/layout-change/main.js @@ -3,6 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; +const app = require("sdk/system/xul-app"); // This test makes sure that require statements used by all AMO hosted // add-ons will be able to use old require statements. @@ -16,14 +17,18 @@ exports["test compatibility"] = function(assert) { assert.equal(require("tabs"), require("sdk/tabs"), "sdk/tabs -> tabs"); - assert.equal(require("widget"), - require("sdk/widget"), "sdk/widget -> widget"); + if (app.is("Firefox")) { + assert.equal(require("widget"), + require("sdk/widget"), "sdk/widget -> widget"); + } assert.equal(require("page-mod"), require("sdk/page-mod"), "sdk/page-mod -> page-mod"); - assert.equal(require("panel"), - require("sdk/panel"), "sdk/panel -> panel"); + if (app.is("Firefox")) { + assert.equal(require("panel"), + require("sdk/panel"), "sdk/panel -> panel"); + } assert.equal(require("request"), require("sdk/request"), "sdk/request -> request"); @@ -34,8 +39,10 @@ exports["test compatibility"] = function(assert) { assert.equal(require("simple-storage"), require("sdk/simple-storage"), "sdk/simple-storage -> simple-storage"); - assert.equal(require("context-menu"), - require("sdk/context-menu"), "sdk/context-menu -> context-menu"); + if (app.is("Firefox")) { + assert.equal(require("context-menu"), + require("sdk/context-menu"), "sdk/context-menu -> context-menu"); + } assert.equal(require("notifications"), require("sdk/notifications"), "sdk/notifications -> notifications"); @@ -49,8 +56,10 @@ exports["test compatibility"] = function(assert) { assert.equal(require("url"), require("sdk/url"), "sdk/url -> url"); - assert.equal(require("selection"), - require("sdk/selection"), "sdk/selection -> selection"); + if (app.is("Firefox")) { + assert.equal(require("selection"), + require("sdk/selection"), "sdk/selection -> selection"); + } assert.equal(require("timers"), require("sdk/timers"), "sdk/timers -> timers"); @@ -97,8 +106,10 @@ exports["test compatibility"] = function(assert) { assert.equal(require("match-pattern"), require("sdk/page-mod/match-pattern"), "sdk/page-mod/match-pattern -> match-pattern"); - assert.equal(require("tab-browser"), - require("sdk/deprecated/tab-browser"), "sdk/deprecated/tab-browser -> tab-browser"); + if (app.is("Firefox")) { + assert.equal(require("tab-browser"), + require("sdk/deprecated/tab-browser"), "sdk/deprecated/tab-browser -> tab-browser"); + } assert.equal(require("file"), require("sdk/io/file"), "sdk/io/file -> file"); @@ -154,8 +165,12 @@ exports["test compatibility"] = function(assert) { assert.equal(require("environment"), require("sdk/system/environment"), "sdk/system/environment -> environment"); - assert.equal(require("utils/data"), - require("sdk/io/data"), "sdk/io/data -> utils/data"); + if (app.is("Firefox")) { + // 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"), require("sdk/test/assert"), "sdk/test/assert -> test/assert"); @@ -174,14 +189,4 @@ exports["test compatibility"] = function(assert) { "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); diff --git a/addon-sdk/source/test/addons/private-browsing-supported/main.js b/addon-sdk/source/test/addons/private-browsing-supported/main.js index 30cf1c15d0b2..f4a3f55203af 100644 --- a/addon-sdk/source/test/addons/private-browsing-supported/main.js +++ b/addon-sdk/source/test/addons/private-browsing-supported/main.js @@ -3,173 +3,22 @@ * 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 { merge } = require('sdk/util/object'); - -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); - } - }) - } -} +const app = require("sdk/system/xul-app"); +const { isGlobalPBSupported } = require('sdk/private-browsing/utils'); merge(module.exports, - require('./test-windows'), require('./test-tabs'), require('./test-page-mod'), 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); diff --git a/addon-sdk/source/test/addons/private-browsing-supported/test-global-private-browsing.js b/addon-sdk/source/test/addons/private-browsing-supported/test-global-private-browsing.js new file mode 100644 index 000000000000..86bf9dad9ba1 --- /dev/null +++ b/addon-sdk/source/test/addons/private-browsing-supported/test-global-private-browsing.js @@ -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(); +}; diff --git a/addon-sdk/source/test/addons/private-browsing-supported/test-private-browsing.js b/addon-sdk/source/test/addons/private-browsing-supported/test-private-browsing.js new file mode 100644 index 000000000000..2c9db66cc16b --- /dev/null +++ b/addon-sdk/source/test/addons/private-browsing-supported/test-private-browsing.js @@ -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); + } + }) + } +} diff --git a/addon-sdk/source/test/addons/require/main.js b/addon-sdk/source/test/addons/require/main.js index 31fd42fe2805..03972abd2475 100644 --- a/addon-sdk/source/test/addons/require/main.js +++ b/addon-sdk/source/test/addons/require/main.js @@ -12,13 +12,13 @@ exports["test local 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 // the only way to require 3rd party package modules are to use absolute paths - // require("panel/main"), require("panel/page-mod"), - // require("panel/third-party") and also require("panel") which will refer - // to panel's main package module. + // require("tabs/main"), require("tabs/page-mod"), + // require("tabs/third-party") and also require("tabs") which will refer + // to tabs's main package module. // So require(page-mod) shouldn't map the 3rd party assert.equal(require("page-mod"), @@ -27,27 +27,27 @@ exports["test 3rd party vs sdk module"] = function (assert) { assert.ok(require("page-mod").PageMod, "page-mod module is really the sdk one"); - assert.equal(require("panel/page-mod").id, "page-mod", - "panel/page-mod is the 3rd party"); + assert.equal(require("tabs/page-mod").id, "page-mod", + "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 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"); - assert.equal(require("panel"), - require("panel/main"), - "require(panel) maps to require(panel/main)"); + assert.equal(require("tabs"), + require("tabs/main"), + "require(tabs) maps to require(tabs/main)"); // So that you have to use relative path to ensure getting the local module - assert.equal(require("./panel").id, - "local-panel", - "require(./panel) maps to the local module"); + assert.equal(require("./tabs").id, + "local-tabs", + "require(./tabs) maps to the local module"); // 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"); - assert.equal(require("sdk/panel"), - require("addon-kit/panel"), + assert.equal(require("sdk/tabs"), + require("addon-kit/tabs"), "Old and new layout both work"); } @@ -70,8 +70,8 @@ exports.testMultipleRequirePerLine = function (assert) { } exports.testSDKRequire = function (assert) { - assert.deepEqual(Object.keys(require('sdk/widget')), ['Widget']); - assert.equal(require('widget'), require('sdk/widget')); + assert.deepEqual(Object.keys(require('sdk/page-worker')), ['Page']); + assert.equal(require('page-worker'), require('sdk/page-worker')); } require("sdk/test/runner").runTestsFromModule(module); diff --git a/addon-sdk/source/test/addons/require/packages/tabs/main.js b/addon-sdk/source/test/addons/require/packages/tabs/main.js new file mode 100644 index 000000000000..871c9e4de32f --- /dev/null +++ b/addon-sdk/source/test/addons/require/packages/tabs/main.js @@ -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"; diff --git a/addon-sdk/source/test/addons/require/packages/tabs/package.json b/addon-sdk/source/test/addons/require/packages/tabs/package.json new file mode 100644 index 000000000000..2446c2e53df1 --- /dev/null +++ b/addon-sdk/source/test/addons/require/packages/tabs/package.json @@ -0,0 +1,3 @@ +{ + "id": "test-panel" +} \ No newline at end of file diff --git a/addon-sdk/source/test/addons/require/packages/tabs/page-mod.js b/addon-sdk/source/test/addons/require/packages/tabs/page-mod.js new file mode 100644 index 000000000000..6c90f46c1a6f --- /dev/null +++ b/addon-sdk/source/test/addons/require/packages/tabs/page-mod.js @@ -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"; diff --git a/addon-sdk/source/test/addons/require/tabs.js b/addon-sdk/source/test/addons/require/tabs.js new file mode 100644 index 000000000000..5a46e63b873a --- /dev/null +++ b/addon-sdk/source/test/addons/require/tabs.js @@ -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"; diff --git a/addon-sdk/source/test/private-browsing/global.js b/addon-sdk/source/test/private-browsing/global.js index 332e95f8dbf6..22ac59e67e9d 100644 --- a/addon-sdk/source/test/private-browsing/global.js +++ b/addon-sdk/source/test/private-browsing/global.js @@ -6,6 +6,7 @@ const timer = require("sdk/timers"); const { LoaderWithHookedConsole, deactivate, pb, pbUtils } = require("./helper"); const tabs = require("sdk/tabs"); +const { getMostRecentBrowserWindow, isWindowPrivate } = require('sdk/window/utils'); exports["test activate private mode via handler"] = function(test) { test.waitUntilDone(); @@ -232,3 +233,18 @@ exports.testUnloadWhileActive = function(test) { 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(); +}; diff --git a/addon-sdk/source/test/private-browsing/helper.js b/addon-sdk/source/test/private-browsing/helper.js index d7b49c053c17..efac72df41ef 100644 --- a/addon-sdk/source/test/private-browsing/helper.js +++ b/addon-sdk/source/test/private-browsing/helper.js @@ -9,6 +9,11 @@ const { loader } = LoaderWithHookedConsole(module); const pb = loader.require('sdk/private-browsing'); 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) { let globals = {}; @@ -45,3 +50,40 @@ exports.deactivate = deactivate; exports.pb = pb; exports.pbUtils = pbUtils; 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; +} diff --git a/addon-sdk/source/test/private-browsing/tabs.js b/addon-sdk/source/test/private-browsing/tabs.js index 1c39d9393865..596838ca6823 100644 --- a/addon-sdk/source/test/private-browsing/tabs.js +++ b/addon-sdk/source/test/private-browsing/tabs.js @@ -2,14 +2,17 @@ const { Ci } = require('chrome'); 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 { isPrivate } = require('sdk/private-browsing'); exports.testIsPrivateOnTab = function(test) { let window = browserWindows.activeWindow; + let chromeWindow = getOwnerWindow(window); + 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,

Hi!

', { isPrivate: true @@ -17,8 +20,8 @@ exports.testIsPrivateOnTab = function(test) { // test that the tab is private test.assert(rawTab.browser.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing); - test.assert(pb.isPrivate(rawTab.browser.contentWindow)); - test.assert(pb.isPrivate(rawTab.browser)); + test.assert(isPrivate(rawTab.browser.contentWindow)); + test.assert(isPrivate(rawTab.browser)); closeTab(rawTab); } diff --git a/addon-sdk/source/test/test-content-worker.js b/addon-sdk/source/test/test-content-worker.js index 7448de755f94..346fb52c5194 100644 --- a/addon-sdk/source/test/test-content-worker.js +++ b/addon-sdk/source/test/test-content-worker.js @@ -175,6 +175,7 @@ exports["test:emit hack message"] = WorkerTest( exports["test:n-arguments emit"] = WorkerTest( DEFAULT_CONTENT_URL, function(assert, browser, done) { + let repeat = 0; let worker = Worker({ window: browser.contentWindow, contentScript: "new " + function WorkerScope() { @@ -187,10 +188,14 @@ exports["test:n-arguments emit"] = WorkerTest( // Validate worker.port worker.port.on("content-to-addon", function (arg1, arg2, arg3) { - assert.equal(arg1, "first argument"); - assert.equal(arg2, "second"); - assert.equal(arg3, "third"); - done(); + if (!repeat++) { + this.emit("addon-to-content", "first argument", "second", "third"); + } else { + assert.equal(arg1, "first argument"); + assert.equal(arg2, "second"); + assert.equal(arg3, "third"); + done(); + } }); worker.port.emit("addon-to-content", "first argument", "second", "third"); } diff --git a/addon-sdk/source/test/test-event-core.js b/addon-sdk/source/test/test-event-core.js index 4aa3d599c43d..910d4fc6c3be 100644 --- a/addon-sdk/source/test/test-event-core.js +++ b/addon-sdk/source/test/test-event-core.js @@ -157,10 +157,10 @@ exports['test error handling'] = function(assert) { emit(target, 'message'); }; -exports['test unhandled errors'] = function(assert) { +exports['test unhandled exceptions'] = function(assert) { let exceptions = []; let { loader, messages } = LoaderWithHookedConsole(module); - + let { emit, on } = loader.require('sdk/event/core'); let target = {}; let boom = Error('Boom!'); @@ -182,6 +182,22 @@ exports['test unhandled errors'] = function(assert) { '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) { let target = {}; diff --git a/addon-sdk/source/test/test-hidden-frame.js b/addon-sdk/source/test/test-hidden-frame.js index eeb3c2a6c45f..73fb931b6ebd 100644 --- a/addon-sdk/source/test/test-hidden-frame.js +++ b/addon-sdk/source/test/test-hidden-frame.js @@ -4,6 +4,7 @@ "use strict"; +const { Loader } = require("sdk/test/loader"); const hiddenFrames = require("sdk/frame/hidden-frame"); 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,"; + + 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); diff --git a/addon-sdk/source/test/test-page-mod.js b/addon-sdk/source/test/test-page-mod.js index 9ef6743ee8de..3eb07526e643 100644 --- a/addon-sdk/source/test/test-page-mod.js +++ b/addon-sdk/source/test/test-page-mod.js @@ -9,15 +9,16 @@ const { Loader } = require('sdk/test/loader'); const tabs = require("sdk/tabs"); const timer = require("sdk/timers"); 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 windowHelpers = require('sdk/window/helpers'); const { getTabContentWindow, getActiveTab, setTabURL, openTab, closeTab } = require('sdk/tabs/utils'); 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 { 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 { pb } = require('./private-browsing/helper'); /* 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 @@ -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) { test.waitUntilDone(); 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 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," + + ""; + + 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(); +} diff --git a/addon-sdk/source/test/test-page-worker.js b/addon-sdk/source/test/test-page-worker.js index dc4e9e790f80..cde0dbbc7af3 100644 --- a/addon-sdk/source/test/test-page-worker.js +++ b/addon-sdk/source/test/test-page-worker.js @@ -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) { try { page.postMessage("foo"); diff --git a/addon-sdk/source/test/test-panel.js b/addon-sdk/source/test/test-panel.js index f7ff7450d503..86f9a5c40dae 100644 --- a/addon-sdk/source/test/test-panel.js +++ b/addon-sdk/source/test/test-panel.js @@ -10,9 +10,11 @@ const timer = require("sdk/timers"); const self = require('sdk/self'); const { open, close, focus } = require('sdk/window/helpers'); 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 { 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'); @@ -287,7 +289,10 @@ exports["test Anchor And Arrow"] = function(assert, done) { const { Panel } = require('sdk/panel'); let count = 0; - function newPanel(tab, anchor) { + let queue = []; + let tab; + + function newPanel(anchor) { let panel = Panel({ contentURL: "data:text/html;charset=utf-8,Anchor: " + @@ -295,16 +300,22 @@ exports["test Anchor And Arrow"] = function(assert, done) { width: 200, height: 100, onShow: function () { - count++; panel.destroy(); - if (count==5) { - assert.pass("All anchored panel test displayed"); - tab.close(function () { - done(); - }); - } + next(); } }); + 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); } @@ -321,22 +332,105 @@ exports["test Anchor And Arrow"] = function(assert, done) { tabs.open({ url: url, - onReady: function(tab) { + onReady: function(_tab) { + tab = _tab; let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"]. getService(Ci.nsIWindowMediator). getMostRecentWindow("navigator:browser"); let window = browserWindow.content; - newPanel(tab, window.document.getElementById('tl')); - newPanel(tab, window.document.getElementById('tr')); - newPanel(tab, window.document.getElementById('bl')); - newPanel(tab, window.document.getElementById('br')); + newPanel(window.document.getElementById('tl')); + newPanel(window.document.getElementById('tr')); + newPanel(window.document.getElementById('bl')); + newPanel(window.document.getElementById('br')); 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) { @@ -518,7 +612,7 @@ exports["test console.log in Panel"] = function(assert, done) { }); panel.show(); - + function onMessage(type, message) { assert.equal(type, 'log', 'console.log() works'); assert.equal(message, text, 'console.log() works'); @@ -658,6 +752,106 @@ function testShowPanel(assert, panel) { 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 { require("sdk/panel"); } diff --git a/addon-sdk/source/test/test-tab-utils.js b/addon-sdk/source/test/test-tab-utils.js new file mode 100644 index 000000000000..7d10cc0e3325 --- /dev/null +++ b/addon-sdk/source/test/test-tab-utils.js @@ -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); diff --git a/addon-sdk/source/test/test-tabs-common.js b/addon-sdk/source/test/test-tabs-common.js index 26fc6ad5a6e0..5ec93cb31e52 100644 --- a/addon-sdk/source/test/test-tabs-common.js +++ b/addon-sdk/source/test/test-tabs-common.js @@ -8,9 +8,10 @@ const { browserWindows } = require('sdk/windows'); const tabs = require('sdk/tabs'); const { isPrivate } = require('sdk/private-browsing'); const { openDialog } = require('sdk/window/utils'); -const pbUtils = require('sdk/private-browsing/utils'); const { isWindowPrivate } = require('sdk/window/utils'); 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,#title#'; @@ -323,34 +324,27 @@ exports.testTabOpenPrivate = function(test) { // We need permission flag in order to see private window's tabs exports.testPrivateAreNotListed = function (test) { - test.waitUntilDone(); let originalTabCount = tabs.length; - let win = openDialog({ - private: true - }); + let page = openWebpage("about:blank", true); + if (!page) { + test.pass("Private browsing isn't supported in this release"); + return; + } - win.addEventListener("load", function onload() { - win.removeEventListener("load", onload); - - // PWPB case - if (pbUtils.isWindowPBSupported) { - test.assert(isWindowPrivate(win), "window is private"); + test.waitUntilDone(); + page.ready.then(function (win) { + if (isTabPBSupported || isWindowPBSupported) { + test.assert(isWindowPrivate(win), "the window is private"); 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 { - // Global case, openDialog didn't opened a private window/tab - test.assert(!isWindowPrivate(win), "window is private"); + test.assert(!isWindowPrivate(win), "the window isn't private"); 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'); } - - win.addEventListener("unload", function onunload() { - win.removeEventListener('unload', onunload); - test.done(); - }); - win.close(); + page.close().then(test.done.bind(test)); }); } diff --git a/addon-sdk/source/test/test-window-utils-global-private-browsing.js b/addon-sdk/source/test/test-window-utils-global-private-browsing.js new file mode 100644 index 000000000000..39d7aa61ddb2 --- /dev/null +++ b/addon-sdk/source/test/test-window-utils-global-private-browsing.js @@ -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); diff --git a/addon-sdk/source/test/test-window-utils-private-browsing.js b/addon-sdk/source/test/test-window-utils-private-browsing.js index 67bf366e452f..8b5b37228e28 100644 --- a/addon-sdk/source/test/test-window-utils-private-browsing.js +++ b/addon-sdk/source/test/test-window-utils-private-browsing.js @@ -30,14 +30,10 @@ exports.testWindowTrackerIgnoresPrivateWindows = function(assert, done) { let wt = windowUtils.WindowTracker({ onTrack: function(window) { - if (isWindowPrivate(window)) { - assert.fail('private window was tracked!'); - } + assert.ok(!isWindowPrivate(window), 'private window was not tracked!'); }, onUntrack: function(window) { - if (isWindowPrivate(window)) { - assert.fail('private window was tracked!'); - } + assert.ok(!isWindowPrivate(window), 'private window was not tracked!'); // PWPB case if (window === myPrivateWindow && isWindowPBSupported) { privateWindowClosed = true;