Merge m-c to inbound. a=merge
63
addon-sdk/source/CONTRIBUTING.md
Normal file
@ -0,0 +1,63 @@
|
||||
## Overview
|
||||
|
||||
- Changes should follow the [design guidelines], as well as [coding style guide] for Jetpack
|
||||
- All changes must be accompanied by tests
|
||||
- In order to land, changes must have review from a core Jetpack developer
|
||||
- Changes should have additional API review when needed
|
||||
- Changes should have additional review from a Mozilla platform domain-expert when needed
|
||||
|
||||
If you have questions, ask in [#jetpack on IRC][jetpack irc channel] or on the [Jetpack mailing list].
|
||||
|
||||
## How to Make Code Contributions
|
||||
|
||||
If you have code that you'd like to contribute the Jetpack project, follow these steps:
|
||||
|
||||
1. Look for your issue in the [bugs already filed][open bugs]
|
||||
2. If no bug exists, [submit one][submit bug]
|
||||
3. Make your changes, per the Overview
|
||||
4. Write a test ([intro][test intro], [API][test API])
|
||||
5. Submit pull request with changes and a title in a form of `Bug XXX - description`
|
||||
6. Copy the pull request link from GitHub and paste it in as an attachment to the bug
|
||||
7. Each pull request should idealy contain only one commit, so squash the commits if necessary.
|
||||
8. Flag the attachment for code review from one of the Jetpack reviewers listed below.
|
||||
This step is optional, but could speed things up.
|
||||
9. Address any nits (ie style changes), or other issues mentioned in the review.
|
||||
10. Finally, once review is approved, a team member will do the merging
|
||||
|
||||
## Good First Bugs
|
||||
|
||||
There is a list of [good first bugs here](https://bugzilla.mozilla.org/buglist.cgi?list_id=7345714&columnlist=bug_severity%2Cpriority%2Cassigned_to%2Cbug_status%2Ctarget_milestone%2Cresolution%2Cshort_desc%2Cchangeddate&query_based_on=jetpack-good-1st-bugs&status_whiteboard_type=allwordssubstr&query_format=advanced&status_whiteboard=[good%20first%20bug]&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=VERIFIED&product=Add-on%20SDK&known_name=jetpack-good-1st-bugs).
|
||||
|
||||
## Reviewers
|
||||
|
||||
All changes must be reviewed by someone on the Jetpack review crew:
|
||||
|
||||
- [@mossop]
|
||||
- [@gozala]
|
||||
- [@wbamberg]
|
||||
- [@ZER0]
|
||||
- [@erikvold]
|
||||
- [@jsantell]
|
||||
- [@zombie]
|
||||
|
||||
For review of Mozilla platform usage and best practices, ask [@autonome],
|
||||
[@0c0w3], or [@mossop] to find the domain expert.
|
||||
|
||||
For API and developer ergonomics review, ask [@gozala].
|
||||
|
||||
[design guidelines]:https://wiki.mozilla.org/Labs/Jetpack/Design_Guidelines
|
||||
[jetpack irc channel]:irc://irc.mozilla.org/#jetpack
|
||||
[Jetpack mailing list]:http://groups.google.com/group/mozilla-labs-jetpack
|
||||
[open bugs]:https://bugzilla.mozilla.org/buglist.cgi?quicksearch=product%3ASDK
|
||||
[submit bug]:https://bugzilla.mozilla.org/enter_bug.cgi?product=Add-on%20SDK&component=general
|
||||
[test intro]:https://jetpack.mozillalabs.com/sdk/latest/docs/#guide/implementing-reusable-module
|
||||
[test API]:https://jetpack.mozillalabs.com/sdk/latest/docs/#module/api-utils/unit-test
|
||||
[coding style guide]:https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide
|
||||
|
||||
[@mossop]:https://github.com/mossop/
|
||||
[@gozala]:https://github.com/Gozala/
|
||||
[@wbamberg]:https://github.com/wbamberg/
|
||||
[@ZER0]:https://github.com/ZER0/
|
||||
[@erikvold]:https://github.com/erikvold/
|
||||
[@jsantell]:https://github.com/jsantell
|
||||
[@zombie]:https://github.com/zombie
|
@ -135,9 +135,19 @@ TestFinder.prototype = {
|
||||
let { fileFilter, testFilter } = makeFilters({ filter: this.filter });
|
||||
|
||||
return getSuites({ id: id, filter: fileFilter }).then(suites => {
|
||||
let tests = [];
|
||||
let testsRemaining = [];
|
||||
|
||||
let getNextTest = () => {
|
||||
if (testsRemaining.length) {
|
||||
return testsRemaining.shift();
|
||||
}
|
||||
|
||||
if (!suites.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let suite = suites.shift();
|
||||
|
||||
suites.forEach(suite => {
|
||||
// Load each test file as a main module in its own loader instance
|
||||
// `suite` is defined by cuddlefish/manifest.py:ManifestBuilder.build
|
||||
let suiteModule;
|
||||
@ -162,7 +172,7 @@ TestFinder.prototype = {
|
||||
if (this.testInProcess) {
|
||||
for (let name of Object.keys(suiteModule).sort()) {
|
||||
if (NOT_TESTS.indexOf(name) === -1 && testFilter(name)) {
|
||||
tests.push({
|
||||
testsRemaining.push({
|
||||
setup: suiteModule.setup,
|
||||
teardown: suiteModule.teardown,
|
||||
testFunction: suiteModule[name],
|
||||
@ -171,9 +181,13 @@ TestFinder.prototype = {
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return tests;
|
||||
return getNextTest();
|
||||
};
|
||||
|
||||
return {
|
||||
getNext: () => resolve(getNextTest())
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -11,8 +11,9 @@ const memory = require("./memory");
|
||||
const timer = require("../timers");
|
||||
const cfxArgs = require("../test/options");
|
||||
const { getTabs, closeTab, getURI } = require("../tabs/utils");
|
||||
const { windows, isBrowser } = require("../window/utils");
|
||||
const { windows, isBrowser, getMostRecentBrowserWindow } = require("../window/utils");
|
||||
const { defer, all, Debugging: PromiseDebugging } = require("../core/promise");
|
||||
const { getInnerId } = require("../window/utils");
|
||||
|
||||
const findAndRunTests = function findAndRunTests(options) {
|
||||
var TestFinder = require("./unit-test-finder").TestFinder;
|
||||
@ -32,11 +33,13 @@ const findAndRunTests = function findAndRunTests(options) {
|
||||
};
|
||||
exports.findAndRunTests = findAndRunTests;
|
||||
|
||||
let runnerWindows = new WeakMap();
|
||||
|
||||
const TestRunner = function TestRunner(options) {
|
||||
if (options) {
|
||||
this.fs = options.fs;
|
||||
}
|
||||
this.console = (options && "console" in options) ? options.console : console;
|
||||
options = options || {};
|
||||
runnerWindows.set(this, getInnerId(getMostRecentBrowserWindow()));
|
||||
this.fs = options.fs;
|
||||
this.console = options.console || console;
|
||||
memory.track(this);
|
||||
this.passed = 0;
|
||||
this.failed = 0;
|
||||
@ -314,7 +317,7 @@ TestRunner.prototype = {
|
||||
}
|
||||
let leftover = tabs.slice(1);
|
||||
|
||||
if (wins.length != 1)
|
||||
if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this))
|
||||
this.fail("Should not be any unexpected windows open");
|
||||
if (tabs.length != 1)
|
||||
this.fail("Should not be any unexpected tabs open");
|
||||
@ -483,17 +486,23 @@ TestRunner.prototype = {
|
||||
|
||||
startMany: function startMany(options) {
|
||||
function runNextTest(self) {
|
||||
var test = options.tests.shift();
|
||||
if (options.stopOnError && self.test && self.test.failed) {
|
||||
self.console.error("aborted: test failed and --stop-on-error was specified");
|
||||
options.onDone(self);
|
||||
} else if (test) {
|
||||
self.start({test: test, onDone: runNextTest});
|
||||
} else {
|
||||
options.onDone(self);
|
||||
}
|
||||
let { tests, onDone } = options;
|
||||
|
||||
return tests.getNext().then((test) => {
|
||||
if (options.stopOnError && self.test && self.test.failed) {
|
||||
self.console.error("aborted: test failed and --stop-on-error was specified");
|
||||
onDone(self);
|
||||
}
|
||||
else if (test) {
|
||||
self.start({test: test, onDone: runNextTest});
|
||||
}
|
||||
else {
|
||||
onDone(self);
|
||||
}
|
||||
});
|
||||
}
|
||||
runNextTest(this);
|
||||
|
||||
return runNextTest(this).catch(console.exception);
|
||||
},
|
||||
|
||||
start: function start(options) {
|
||||
|
@ -10,6 +10,7 @@ module.metadata = {
|
||||
var { exit, stdout } = require("../system");
|
||||
var cfxArgs = require("../test/options");
|
||||
var events = require("../system/events");
|
||||
const { resolve } = require("../core/promise");
|
||||
|
||||
function runTests(findAndRunTests) {
|
||||
var harness = require("./harness");
|
||||
@ -120,7 +121,9 @@ exports.runTestsFromModule = function runTestsFromModule(module) {
|
||||
var { TestRunner } = loader.require("../deprecated/unit-test");
|
||||
var runner = new TestRunner();
|
||||
runner.startMany({
|
||||
tests: tests,
|
||||
tests: {
|
||||
getNext: () => resolve(tests.shift())
|
||||
},
|
||||
stopOnError: cfxArgs.stopOnError,
|
||||
onDone: nextIteration
|
||||
});
|
||||
|
@ -22,8 +22,8 @@ DEFAULT_ICON = 'icon.png'
|
||||
DEFAULT_ICON64 = 'icon64.png'
|
||||
|
||||
METADATA_PROPS = ['name', 'description', 'keywords', 'author', 'version',
|
||||
'translators', 'contributors', 'license', 'homepage', 'icon',
|
||||
'icon64', 'main', 'directories', 'permissions', 'preferences']
|
||||
'developers', 'translators', 'contributors', 'license', 'homepage',
|
||||
'icon', 'icon64', 'main', 'directories', 'permissions', 'preferences']
|
||||
|
||||
RESOURCE_HOSTNAME_RE = re.compile(r'^[a-z0-9_\-]+$')
|
||||
|
||||
|
@ -138,6 +138,11 @@ def gen_manifest(template_root_dir, target_cfg, jid,
|
||||
elem.appendChild(dom.createTextNode(translator))
|
||||
dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)
|
||||
|
||||
for developer in target_cfg.get("developers", [ ]):
|
||||
elem = dom.createElement("em:developer");
|
||||
elem.appendChild(dom.createTextNode(developer))
|
||||
dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem)
|
||||
|
||||
for contributor in target_cfg.get("contributors", [ ]):
|
||||
elem = dom.createElement("em:contributor");
|
||||
elem.appendChild(dom.createTextNode(contributor))
|
||||
@ -150,7 +155,7 @@ def gen_manifest(template_root_dir, target_cfg, jid,
|
||||
|
||||
if target_cfg.get("preferences"):
|
||||
manifest.set("em:optionsType", "2")
|
||||
|
||||
|
||||
# workaround until bug 971249 is fixed
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=971249
|
||||
manifest.set("em:optionsURL", "data:text/xml,<placeholder/>")
|
||||
|
23
addon-sdk/source/test/addons/developers/main.js
Normal file
@ -0,0 +1,23 @@
|
||||
/* 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 { Cu } = require('chrome');
|
||||
const { id } = require('sdk/self');
|
||||
const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
|
||||
|
||||
exports.testDevelopers = function(assert, done) {
|
||||
AddonManager.getAddonByID(id, (addon) => {
|
||||
let count = 0;
|
||||
addon.developers.forEach(({ name }) => {
|
||||
count++;
|
||||
assert.equal(name, count == 1 ? 'A' : 'B', 'The developers keys are correct');
|
||||
});
|
||||
assert.equal(count, 2, 'The key count is correct');
|
||||
assert.equal(addon.developers.length, 2, 'The key length is correct');
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
require('sdk/test/runner').runTestsFromModule(module);
|
6
addon-sdk/source/test/addons/developers/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"id": "test-developers@jetpack",
|
||||
"title": "Test developers package key",
|
||||
"author": "Erik Vold",
|
||||
"developers": [ "A", "B" ]
|
||||
}
|
@ -93,39 +93,29 @@ exports.testIsPrivateOnWindowOpenFromPrivate = function(assert, done) {
|
||||
then(done, assert.fail);
|
||||
};
|
||||
|
||||
exports.testOpenTabWithPrivateWindow = function(assert, done) {
|
||||
function start() {
|
||||
openPromise(null, {
|
||||
features: {
|
||||
private: true,
|
||||
toolbar: true
|
||||
}
|
||||
}).then(focus).then(function(window) {
|
||||
let { promise, resolve } = defer();
|
||||
assert.equal(isPrivate(window), true, 'the focused window is private');
|
||||
exports.testOpenTabWithPrivateWindow = function*(assert) {
|
||||
let { promise, resolve } = defer();
|
||||
|
||||
tabs.open({
|
||||
url: 'about:blank',
|
||||
onOpen: function(tab) {
|
||||
assert.equal(isPrivate(tab), false, 'the opened tab is not private');
|
||||
// not closing this tab on purpose.. for now...
|
||||
// we keep this tab open because we closed all windows
|
||||
// and must keep a non-private window open at end of this test for next ones.
|
||||
resolve(window);
|
||||
}
|
||||
});
|
||||
|
||||
return promise;
|
||||
}).then(close).then(done, assert.fail);
|
||||
}
|
||||
|
||||
(function closeWindows() {
|
||||
if (windows.length > 0) {
|
||||
return windows.activeWindow.close(closeWindows);
|
||||
let window = yield openPromise(null, {
|
||||
features: {
|
||||
private: true,
|
||||
toolbar: true
|
||||
}
|
||||
assert.pass('all pre test windows have been closed');
|
||||
return start();
|
||||
})()
|
||||
});
|
||||
yield focus(window);
|
||||
|
||||
assert.equal(isPrivate(window), true, 'the focused window is private');
|
||||
|
||||
tabs.open({
|
||||
url: 'about:blank',
|
||||
onOpen: (tab) => {
|
||||
assert.equal(isPrivate(tab), false, 'the opened tab is not private');
|
||||
tab.close(resolve);
|
||||
}
|
||||
});
|
||||
|
||||
yield promise;
|
||||
yield close(window);
|
||||
};
|
||||
|
||||
exports.testIsPrivateOnWindowOff = function(assert, done) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
/* 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 = {
|
||||
@ -25,8 +24,7 @@ const FRAME_URL = "data:text/html;charset=utf-8," + encodeURIComponent(FRAME_HTM
|
||||
|
||||
const { defer } = require("sdk/core/promise");
|
||||
const tabs = require("sdk/tabs");
|
||||
const { setTabURL } = require("sdk/tabs/utils");
|
||||
const { getActiveTab, getTabContentWindow, closeTab } = require("sdk/tabs/utils")
|
||||
const { getActiveTab, getTabContentWindow, closeTab, setTabURL } = require("sdk/tabs/utils")
|
||||
const { getMostRecentBrowserWindow } = require("sdk/window/utils");
|
||||
const { open: openNewWindow, close: closeWindow } = require("sdk/window/helpers");
|
||||
const { Loader } = require("sdk/test/loader");
|
||||
@ -293,403 +291,390 @@ function createEmptySelections(window) {
|
||||
|
||||
// Test cases
|
||||
|
||||
exports["test No Selection"] = function(assert, done) {
|
||||
exports["test No Selection"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let window = yield open(URL);
|
||||
|
||||
open(URL).then(function() {
|
||||
assert.equal(selection.isContiguous, false,
|
||||
"selection.isContiguous without selection works.");
|
||||
|
||||
assert.equal(selection.isContiguous, false,
|
||||
"selection.isContiguous without selection works.");
|
||||
assert.strictEqual(selection.text, null,
|
||||
"selection.text without selection works.");
|
||||
|
||||
assert.strictEqual(selection.text, null,
|
||||
"selection.text without selection works.");
|
||||
assert.strictEqual(selection.html, null,
|
||||
"selection.html without selection works.");
|
||||
|
||||
assert.strictEqual(selection.html, null,
|
||||
"selection.html without selection works.");
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection)
|
||||
selectionCount++;
|
||||
|
||||
let selectionCount = 0;
|
||||
for each (let sel in selection)
|
||||
selectionCount++;
|
||||
assert.equal(selectionCount, 0, "No iterable selections");
|
||||
|
||||
assert.equal(selectionCount, 0,
|
||||
"No iterable selections");
|
||||
|
||||
}).then(close).then(loader.unload).then(done, assert.fail);
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports["test Single DOM Selection"] = function(assert, done) {
|
||||
exports["test Single DOM Selection"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let window = yield open(URL);
|
||||
|
||||
open(URL).then(selectFirstDiv).then(function() {
|
||||
selectFirstDiv(window)
|
||||
|
||||
assert.equal(selection.isContiguous, true,
|
||||
"selection.isContiguous with single DOM Selection works.");
|
||||
assert.equal(selection.isContiguous, true,
|
||||
"selection.isContiguous with single DOM Selection works.");
|
||||
|
||||
assert.equal(selection.text, "foo",
|
||||
"selection.text with single DOM Selection works.");
|
||||
assert.equal(selection.text, "foo",
|
||||
"selection.text with single DOM Selection works.");
|
||||
|
||||
assert.equal(selection.html, "<div>foo</div>",
|
||||
"selection.html with single DOM Selection works.");
|
||||
assert.equal(selection.html, "<div>foo</div>",
|
||||
"selection.html with single DOM Selection works.");
|
||||
|
||||
let selectionCount = 0;
|
||||
for each (let sel in selection) {
|
||||
selectionCount++;
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection) {
|
||||
selectionCount++;
|
||||
|
||||
assert.equal(sel.text, "foo",
|
||||
"iterable selection.text with single DOM Selection works.");
|
||||
assert.equal(sel.text, "foo",
|
||||
"iterable selection.text with single DOM Selection works.");
|
||||
|
||||
assert.equal(sel.html, "<div>foo</div>",
|
||||
"iterable selection.html with single DOM Selection works.");
|
||||
}
|
||||
assert.equal(sel.html, "<div>foo</div>",
|
||||
"iterable selection.html with single DOM Selection works.");
|
||||
}
|
||||
|
||||
assert.equal(selectionCount, 1,
|
||||
"One iterable selection");
|
||||
assert.equal(selectionCount, 1, "One iterable selection");
|
||||
|
||||
}).then(close).then(loader.unload).then(done, assert.fail);
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports["test Multiple DOM Selection"] = function(assert, done) {
|
||||
exports["test Multiple DOM Selection"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let expectedText = ["foo", "and"];
|
||||
let expectedHTML = ["<div>foo</div>", "<div>and</div>"];
|
||||
let window = yield open(URL);
|
||||
|
||||
open(URL).then(selectAllDivs).then(function() {
|
||||
let expectedText = ["foo", "and"];
|
||||
let expectedHTML = ["<div>foo</div>", "<div>and</div>"];
|
||||
selectAllDivs(window);
|
||||
|
||||
assert.equal(selection.isContiguous, false,
|
||||
"selection.isContiguous with multiple DOM Selection works.");
|
||||
assert.equal(selection.isContiguous, false,
|
||||
"selection.isContiguous with multiple DOM Selection works.");
|
||||
|
||||
assert.equal(selection.text, expectedText[0],
|
||||
"selection.text with multiple DOM Selection works.");
|
||||
assert.equal(selection.text, expectedText[0],
|
||||
"selection.text with multiple DOM Selection works.");
|
||||
|
||||
assert.equal(selection.html, expectedHTML[0],
|
||||
"selection.html with multiple DOM Selection works.");
|
||||
assert.equal(selection.html, expectedHTML[0],
|
||||
"selection.html with multiple DOM Selection works.");
|
||||
|
||||
let selectionCount = 0;
|
||||
for each (let sel in selection) {
|
||||
assert.equal(sel.text, expectedText[selectionCount],
|
||||
"iterable selection.text with multiple DOM Selection works.");
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection) {
|
||||
assert.equal(sel.text, expectedText[selectionCount],
|
||||
"iterable selection.text with multiple DOM Selection works.");
|
||||
|
||||
assert.equal(sel.html, expectedHTML[selectionCount],
|
||||
"iterable selection.text with multiple DOM Selection works.");
|
||||
assert.equal(sel.html, expectedHTML[selectionCount],
|
||||
"iterable selection.text with multiple DOM Selection works.");
|
||||
|
||||
selectionCount++;
|
||||
}
|
||||
selectionCount++;
|
||||
}
|
||||
|
||||
assert.equal(selectionCount, 2,
|
||||
"Two iterable selections");
|
||||
assert.equal(selectionCount, 2, "Two iterable selections");
|
||||
|
||||
}).then(close).then(loader.unload).then(done, assert.fail);
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports["test Textarea Selection"] = function(assert, done) {
|
||||
exports["test Textarea Selection"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let window = yield open(URL);
|
||||
|
||||
open(URL).then(selectTextarea).then(function() {
|
||||
selectTextarea(window);
|
||||
|
||||
assert.equal(selection.isContiguous, true,
|
||||
"selection.isContiguous with Textarea Selection works.");
|
||||
assert.equal(selection.isContiguous, true,
|
||||
"selection.isContiguous with Textarea Selection works.");
|
||||
|
||||
assert.equal(selection.text, "noodles",
|
||||
"selection.text with Textarea Selection works.");
|
||||
assert.equal(selection.text, "noodles",
|
||||
"selection.text with Textarea Selection works.");
|
||||
|
||||
assert.strictEqual(selection.html, null,
|
||||
"selection.html with Textarea Selection works.");
|
||||
assert.strictEqual(selection.html, null,
|
||||
"selection.html with Textarea Selection works.");
|
||||
|
||||
let selectionCount = 0;
|
||||
for each (let sel in selection) {
|
||||
selectionCount++;
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection) {
|
||||
selectionCount++;
|
||||
|
||||
assert.equal(sel.text, "noodles",
|
||||
"iterable selection.text with Textarea Selection works.");
|
||||
assert.equal(sel.text, "noodles",
|
||||
"iterable selection.text with Textarea Selection works.");
|
||||
|
||||
assert.strictEqual(sel.html, null,
|
||||
"iterable selection.html with Textarea Selection works.");
|
||||
}
|
||||
assert.strictEqual(sel.html, null,
|
||||
"iterable selection.html with Textarea Selection works.");
|
||||
}
|
||||
|
||||
assert.equal(selectionCount, 1,
|
||||
"One iterable selection");
|
||||
assert.equal(selectionCount, 1, "One iterable selection");
|
||||
|
||||
}).then(close).then(loader.unload).then(done, assert.fail);
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports["test Set Text in Multiple DOM Selection"] = function(assert, done) {
|
||||
exports["test Set Text in Multiple DOM Selection"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let expectedText = ["bar", "and"];
|
||||
let expectedHTML = ["bar", "<div>and</div>"];
|
||||
let window = yield open(URL);
|
||||
|
||||
open(URL).then(selectAllDivs).then(function() {
|
||||
let expectedText = ["bar", "and"];
|
||||
let expectedHTML = ["bar", "<div>and</div>"];
|
||||
selectAllDivs(window);
|
||||
|
||||
selection.text = "bar";
|
||||
selection.text = "bar";
|
||||
|
||||
assert.equal(selection.text, expectedText[0],
|
||||
"set selection.text with single DOM Selection works.");
|
||||
assert.equal(selection.text, expectedText[0],
|
||||
"set selection.text with single DOM Selection works.");
|
||||
|
||||
assert.equal(selection.html, expectedHTML[0],
|
||||
"selection.html with single DOM Selection works.");
|
||||
assert.equal(selection.html, expectedHTML[0],
|
||||
"selection.html with single DOM Selection works.");
|
||||
|
||||
let selectionCount = 0;
|
||||
for each (let sel in selection) {
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection) {
|
||||
assert.equal(sel.text, expectedText[selectionCount],
|
||||
"iterable selection.text with multiple DOM Selection works.");
|
||||
|
||||
assert.equal(sel.text, expectedText[selectionCount],
|
||||
"iterable selection.text with multiple DOM Selection works.");
|
||||
assert.equal(sel.html, expectedHTML[selectionCount],
|
||||
"iterable selection.html with multiple DOM Selection works.");
|
||||
|
||||
assert.equal(sel.html, expectedHTML[selectionCount],
|
||||
"iterable selection.html with multiple DOM Selection works.");
|
||||
selectionCount++;
|
||||
}
|
||||
|
||||
selectionCount++;
|
||||
}
|
||||
assert.equal(selectionCount, 2, "Two iterable selections");
|
||||
|
||||
assert.equal(selectionCount, 2,
|
||||
"Two iterable selections");
|
||||
|
||||
}).then(close).then(loader.unload).then(done, assert.fail);
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports["test Set HTML in Multiple DOM Selection"] = function(assert, done) {
|
||||
exports["test Set HTML in Multiple DOM Selection"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let html = "<span>b<b>a</b>r</span>";
|
||||
let expectedText = ["bar", "and"];
|
||||
let expectedHTML = [html, "<div>and</div>"];
|
||||
let window = yield open(URL);
|
||||
|
||||
open(URL).then(selectAllDivs).then(function() {
|
||||
let html = "<span>b<b>a</b>r</span>";
|
||||
selectAllDivs(window);
|
||||
|
||||
let expectedText = ["bar", "and"];
|
||||
let expectedHTML = [html, "<div>and</div>"];
|
||||
selection.html = html;
|
||||
|
||||
selection.html = html;
|
||||
assert.equal(selection.text, expectedText[0],
|
||||
"set selection.text with DOM Selection works.");
|
||||
|
||||
assert.equal(selection.text, expectedText[0],
|
||||
"set selection.text with DOM Selection works.");
|
||||
assert.equal(selection.html, expectedHTML[0],
|
||||
"selection.html with DOM Selection works.");
|
||||
|
||||
assert.equal(selection.html, expectedHTML[0],
|
||||
"selection.html with DOM Selection works.");
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection) {
|
||||
assert.equal(sel.text, expectedText[selectionCount],
|
||||
"iterable selection.text with multiple DOM Selection works.");
|
||||
|
||||
let selectionCount = 0;
|
||||
for each (let sel in selection) {
|
||||
assert.equal(sel.html, expectedHTML[selectionCount],
|
||||
"iterable selection.html with multiple DOM Selection works.");
|
||||
|
||||
assert.equal(sel.text, expectedText[selectionCount],
|
||||
"iterable selection.text with multiple DOM Selection works.");
|
||||
selectionCount++;
|
||||
}
|
||||
|
||||
assert.equal(sel.html, expectedHTML[selectionCount],
|
||||
"iterable selection.html with multiple DOM Selection works.");
|
||||
assert.equal(selectionCount, 2, "Two iterable selections");
|
||||
|
||||
selectionCount++;
|
||||
}
|
||||
|
||||
assert.equal(selectionCount, 2,
|
||||
"Two iterable selections");
|
||||
|
||||
}).then(close).then(loader.unload).then(done, assert.fail);
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports["test Set HTML as text in Multiple DOM Selection"] = function(assert, done) {
|
||||
exports["test Set HTML as text in Multiple DOM Selection"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let text = "<span>b<b>a</b>r</span>";
|
||||
let html = "<span>b<b>a</b>r</span>";
|
||||
let expectedText = [text, "and"];
|
||||
let expectedHTML = [html, "<div>and</div>"];
|
||||
let window = yield open(URL);
|
||||
|
||||
open(URL).then(selectAllDivs).then(function() {
|
||||
let text = "<span>b<b>a</b>r</span>";
|
||||
let html = "<span>b<b>a</b>r</span>";
|
||||
selectAllDivs(window);
|
||||
|
||||
let expectedText = [text, "and"];
|
||||
let expectedHTML = [html, "<div>and</div>"];
|
||||
selection.text = text;
|
||||
|
||||
selection.text = text;
|
||||
assert.equal(selection.text, expectedText[0],
|
||||
"set selection.text with DOM Selection works.");
|
||||
|
||||
assert.equal(selection.text, expectedText[0],
|
||||
"set selection.text with DOM Selection works.");
|
||||
assert.equal(selection.html, expectedHTML[0],
|
||||
"selection.html with DOM Selection works.");
|
||||
|
||||
assert.equal(selection.html, expectedHTML[0],
|
||||
"selection.html with DOM Selection works.");
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection) {
|
||||
assert.equal(sel.text, expectedText[selectionCount],
|
||||
"iterable selection.text with multiple DOM Selection works.");
|
||||
|
||||
let selectionCount = 0;
|
||||
for each (let sel in selection) {
|
||||
assert.equal(sel.html, expectedHTML[selectionCount],
|
||||
"iterable selection.html with multiple DOM Selection works.");
|
||||
|
||||
assert.equal(sel.text, expectedText[selectionCount],
|
||||
"iterable selection.text with multiple DOM Selection works.");
|
||||
selectionCount++;
|
||||
}
|
||||
|
||||
assert.equal(sel.html, expectedHTML[selectionCount],
|
||||
"iterable selection.html with multiple DOM Selection works.");
|
||||
assert.equal(selectionCount, 2, "Two iterable selections");
|
||||
|
||||
selectionCount++;
|
||||
}
|
||||
|
||||
assert.equal(selectionCount, 2,
|
||||
"Two iterable selections");
|
||||
|
||||
}).then(close).then(loader.unload).then(done, assert.fail);
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports["test Set Text in Textarea Selection"] = function(assert, done) {
|
||||
exports["test Set Text in Textarea Selection"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let text = "bar";
|
||||
let window = yield open(URL);
|
||||
|
||||
open(URL).then(selectTextarea).then(function() {
|
||||
selectTextarea(window);
|
||||
|
||||
let text = "bar";
|
||||
selection.text = text;
|
||||
|
||||
selection.text = text;
|
||||
assert.equal(selection.text, text,
|
||||
"set selection.text with Textarea Selection works.");
|
||||
|
||||
assert.equal(selection.text, text,
|
||||
"set selection.text with Textarea Selection works.");
|
||||
assert.strictEqual(selection.html, null,
|
||||
"selection.html with Textarea Selection works.");
|
||||
|
||||
assert.strictEqual(selection.html, null,
|
||||
"selection.html with Textarea Selection works.");
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection) {
|
||||
selectionCount++;
|
||||
|
||||
let selectionCount = 0;
|
||||
for each (let sel in selection) {
|
||||
selectionCount++;
|
||||
assert.equal(sel.text, text,
|
||||
"iterable selection.text with Textarea Selection works.");
|
||||
|
||||
assert.equal(sel.text, text,
|
||||
"iterable selection.text with Textarea Selection works.");
|
||||
assert.strictEqual(sel.html, null,
|
||||
"iterable selection.html with Textarea Selection works.");
|
||||
}
|
||||
|
||||
assert.strictEqual(sel.html, null,
|
||||
"iterable selection.html with Textarea Selection works.");
|
||||
}
|
||||
assert.equal(selectionCount, 1, "One iterable selection");
|
||||
|
||||
assert.equal(selectionCount, 1,
|
||||
"One iterable selection");
|
||||
|
||||
}).then(close).then(loader.unload).then(done, assert.fail);
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports["test Set HTML in Textarea Selection"] = function(assert, done) {
|
||||
exports["test Set HTML in Textarea Selection"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let html = "<span>b<b>a</b>r</span>";
|
||||
let window = yield open(URL);
|
||||
|
||||
open(URL).then(selectTextarea).then(function() {
|
||||
selectTextarea(window);
|
||||
|
||||
let html = "<span>b<b>a</b>r</span>";
|
||||
// Textarea can't have HTML so set `html` property is equals to set `text`
|
||||
// property
|
||||
selection.html = html;
|
||||
|
||||
// Textarea can't have HTML so set `html` property is equals to set `text`
|
||||
// property
|
||||
selection.html = html;
|
||||
assert.equal(selection.text, html,
|
||||
"set selection.text with Textarea Selection works.");
|
||||
|
||||
assert.equal(selection.text, html,
|
||||
"set selection.text with Textarea Selection works.");
|
||||
assert.strictEqual(selection.html, null,
|
||||
"selection.html with Textarea Selection works.");
|
||||
|
||||
assert.strictEqual(selection.html, null,
|
||||
"selection.html with Textarea Selection works.");
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection) {
|
||||
selectionCount++;
|
||||
|
||||
let selectionCount = 0;
|
||||
for each (let sel in selection) {
|
||||
selectionCount++;
|
||||
assert.equal(sel.text, html,
|
||||
"iterable selection.text with Textarea Selection works.");
|
||||
|
||||
assert.equal(sel.text, html,
|
||||
"iterable selection.text with Textarea Selection works.");
|
||||
assert.strictEqual(sel.html, null,
|
||||
"iterable selection.html with Textarea Selection works.");
|
||||
}
|
||||
|
||||
assert.strictEqual(sel.html, null,
|
||||
"iterable selection.html with Textarea Selection works.");
|
||||
}
|
||||
assert.equal(selectionCount, 1, "One iterable selection");
|
||||
|
||||
assert.equal(selectionCount, 1,
|
||||
"One iterable selection");
|
||||
|
||||
}).then(close).then(loader.unload).then(done, assert.fail);
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports["test Empty Selections"] = function(assert, done) {
|
||||
exports["test Empty Selections"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let window = yield open(URL);
|
||||
|
||||
open(URL).then(createEmptySelections).then(function(){
|
||||
assert.equal(selection.isContiguous, false,
|
||||
"selection.isContiguous with empty selections works.");
|
||||
createEmptySelections(window);
|
||||
|
||||
assert.strictEqual(selection.text, null,
|
||||
"selection.text with empty selections works.");
|
||||
assert.equal(selection.isContiguous, false,
|
||||
"selection.isContiguous with empty selections works.");
|
||||
|
||||
assert.strictEqual(selection.html, null,
|
||||
"selection.html with empty selections works.");
|
||||
assert.strictEqual(selection.text, null,
|
||||
"selection.text with empty selections works.");
|
||||
|
||||
let selectionCount = 0;
|
||||
for each (let sel in selection)
|
||||
selectionCount++;
|
||||
assert.strictEqual(selection.html, null,
|
||||
"selection.html with empty selections works.");
|
||||
|
||||
assert.equal(selectionCount, 0,
|
||||
"No iterable selections");
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection)
|
||||
selectionCount++;
|
||||
|
||||
}).then(close).then(loader.unload).then(done, assert.fail);
|
||||
assert.equal(selectionCount, 0, "No iterable selections");
|
||||
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
}
|
||||
|
||||
|
||||
exports["test No Selection Exception"] = function(assert, done) {
|
||||
exports["test No Selection Exception"] = function*(assert) {
|
||||
const NO_SELECTION = /It isn't possible to change the selection/;
|
||||
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let window = yield open(URL);
|
||||
|
||||
open(URL).then(function() {
|
||||
// We're trying to change a selection when there is no selection
|
||||
assert.throws(function() {
|
||||
selection.text = "bar";
|
||||
}, NO_SELECTION);
|
||||
|
||||
// We're trying to change a selection when there is no selection
|
||||
assert.throws(function() {
|
||||
selection.text = "bar";
|
||||
}, NO_SELECTION);
|
||||
assert.throws(function() {
|
||||
selection.html = "bar";
|
||||
}, NO_SELECTION);
|
||||
|
||||
assert.throws(function() {
|
||||
selection.html = "bar";
|
||||
}, NO_SELECTION);
|
||||
|
||||
}).then(close).then(loader.unload).then(done, assert.fail);
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports["test for...of without selections"] = function(assert, done) {
|
||||
exports["test for...of without selections"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let window = yield open(URL);
|
||||
let selectionCount = 0;
|
||||
|
||||
open(URL).then(function() {
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection)
|
||||
selectionCount++;
|
||||
|
||||
for (let sel of selection)
|
||||
selectionCount++;
|
||||
assert.equal(selectionCount, 0, "No iterable selections");
|
||||
|
||||
assert.equal(selectionCount, 0,
|
||||
"No iterable selections");
|
||||
|
||||
}).then(close).then(loader.unload).then(null, function(error) {
|
||||
// iterable are not supported yet in Firefox 16, for example, but
|
||||
// they are in Firefox 17.
|
||||
if (error.message.indexOf("is not iterable") > -1)
|
||||
assert.pass("`iterable` method not supported in this application");
|
||||
else
|
||||
assert.fail(error);
|
||||
}).then(done, assert.fail);
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
}
|
||||
|
||||
exports["test for...of with selections"] = function(assert, done) {
|
||||
exports["test for...of with selections"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let expectedText = ["foo", "and"];
|
||||
let expectedHTML = ["<div>foo</div>", "<div>and</div>"];
|
||||
let window = yield open(URL);
|
||||
|
||||
open(URL).then(selectAllDivs).then(function(){
|
||||
let expectedText = ["foo", "and"];
|
||||
let expectedHTML = ["<div>foo</div>", "<div>and</div>"];
|
||||
selectAllDivs(window);
|
||||
|
||||
let selectionCount = 0;
|
||||
let selectionCount = 0;
|
||||
|
||||
for (let sel of selection) {
|
||||
assert.equal(sel.text, expectedText[selectionCount],
|
||||
"iterable selection.text with for...of works.");
|
||||
for (let sel of selection) {
|
||||
assert.equal(sel.text, expectedText[selectionCount],
|
||||
"iterable selection.text with for...of works.");
|
||||
|
||||
assert.equal(sel.html, expectedHTML[selectionCount],
|
||||
"iterable selection.text with for...of works.");
|
||||
assert.equal(sel.html, expectedHTML[selectionCount],
|
||||
"iterable selection.text with for...of works.");
|
||||
|
||||
selectionCount++;
|
||||
}
|
||||
selectionCount++;
|
||||
}
|
||||
|
||||
assert.equal(selectionCount, 2,
|
||||
"Two iterable selections");
|
||||
assert.equal(selectionCount, 2, "Two iterable selections");
|
||||
|
||||
}).then(close).then(loader.unload).then(null, function(error) {
|
||||
// iterable are not supported yet in Firefox 16, for example, but
|
||||
// they are in Firefox 17.
|
||||
if (error.message.indexOf("is not iterable") > -1)
|
||||
assert.pass("`iterable` method not supported in this application");
|
||||
else
|
||||
assert.fail(error);
|
||||
}).then(done, assert.fail)
|
||||
yield close(window);
|
||||
loader.unload();
|
||||
}
|
||||
|
||||
exports["test Selection Listener"] = function(assert, done) {
|
||||
@ -722,7 +707,7 @@ exports["test Textarea OnSelect Listener"] = function(assert, done) {
|
||||
then(dispatchOnSelectEvent, assert.fail);
|
||||
};
|
||||
|
||||
exports["test Selection listener removed on unload"] = function(assert, done) {
|
||||
exports["test Selection listener removed on unload"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
|
||||
@ -731,17 +716,16 @@ exports["test Selection listener removed on unload"] = function(assert, done) {
|
||||
});
|
||||
|
||||
loader.unload();
|
||||
assert.pass("unload was a success");
|
||||
|
||||
assert.pass();
|
||||
|
||||
open(URL).
|
||||
yield open(URL).
|
||||
then(selectContentFirstDiv).
|
||||
then(dispatchSelectionEvent).
|
||||
then(close).
|
||||
then(done, assert.fail);
|
||||
catch(assert.fail);
|
||||
};
|
||||
|
||||
exports["test Textarea onSelect Listener removed on unload"] = function(assert, done) {
|
||||
exports["test Textarea onSelect Listener removed on unload"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
|
||||
@ -750,14 +734,13 @@ exports["test Textarea onSelect Listener removed on unload"] = function(assert,
|
||||
});
|
||||
|
||||
loader.unload();
|
||||
assert.pass("unload was a success");
|
||||
|
||||
assert.pass();
|
||||
|
||||
open(URL).
|
||||
yield open(URL).
|
||||
then(selectTextarea).
|
||||
then(dispatchOnSelectEvent).
|
||||
then(close).
|
||||
then(done, assert.fail);
|
||||
catch(assert.fail);
|
||||
};
|
||||
|
||||
|
||||
@ -848,7 +831,7 @@ exports["test Selection Listener on frame"] = function(assert, done) {
|
||||
then(getFrameWindow).
|
||||
then(selectContentFirstDiv).
|
||||
then(dispatchSelectionEvent).
|
||||
then(null, assert.fail);
|
||||
catch(assert.fail);
|
||||
};
|
||||
|
||||
exports["test Textarea onSelect Listener on frame"] = function(assert, done) {
|
||||
@ -867,11 +850,11 @@ exports["test Textarea onSelect Listener on frame"] = function(assert, done) {
|
||||
then(getFrameWindow).
|
||||
then(selectTextarea).
|
||||
then(dispatchOnSelectEvent).
|
||||
then(null, assert.fail);
|
||||
catch(assert.fail);
|
||||
};
|
||||
|
||||
|
||||
exports["test PBPW Selection Listener"] = function(assert, done) {
|
||||
exports["test PBPW Selection Listener"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
|
||||
@ -881,15 +864,16 @@ exports["test PBPW Selection Listener"] = function(assert, done) {
|
||||
|
||||
assert.pass();
|
||||
|
||||
open(URL, {private: true}).
|
||||
yield open(URL, { private: true }).
|
||||
then(selectContentFirstDiv).
|
||||
then(dispatchSelectionEvent).
|
||||
then(closeWindow).
|
||||
then(loader.unload).
|
||||
then(done, assert.fail);
|
||||
catch(assert.fail);
|
||||
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports["test PBPW Textarea OnSelect Listener"] = function(assert, done) {
|
||||
exports["test PBPW Textarea OnSelect Listener"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
|
||||
@ -899,72 +883,73 @@ exports["test PBPW Textarea OnSelect Listener"] = function(assert, done) {
|
||||
|
||||
assert.pass();
|
||||
|
||||
open(URL, {private: true}).
|
||||
yield open(URL, { private: true }).
|
||||
then(selectTextarea).
|
||||
then(dispatchOnSelectEvent).
|
||||
then(closeWindow).
|
||||
then(loader.unload).
|
||||
then(done, assert.fail);
|
||||
catch(assert.fail);
|
||||
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
|
||||
exports["test PBPW Single DOM Selection"] = function(assert, done) {
|
||||
exports["test PBPW Single DOM Selection"] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let window = yield open(URL, { private: true });
|
||||
|
||||
open(URL, {private: true}).then(selectFirstDiv).then(function(window) {
|
||||
selectFirstDiv(window);
|
||||
|
||||
assert.equal(selection.isContiguous, false,
|
||||
"selection.isContiguous with single DOM Selection in PBPW works.");
|
||||
assert.equal(selection.isContiguous, false,
|
||||
"selection.isContiguous with single DOM Selection in PBPW works.");
|
||||
|
||||
assert.equal(selection.text, null,
|
||||
"selection.text with single DOM Selection in PBPW works.");
|
||||
assert.equal(selection.text, null,
|
||||
"selection.text with single DOM Selection in PBPW works.");
|
||||
|
||||
assert.equal(selection.html, null,
|
||||
"selection.html with single DOM Selection in PBPW works.");
|
||||
assert.equal(selection.html, null,
|
||||
"selection.html with single DOM Selection in PBPW works.");
|
||||
|
||||
let selectionCount = 0;
|
||||
for each (let sel in selection)
|
||||
selectionCount++;
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection)
|
||||
selectionCount++;
|
||||
|
||||
assert.equal(selectionCount, 0,
|
||||
"No iterable selection in PBPW");
|
||||
assert.equal(selectionCount, 0, "No iterable selection in PBPW");
|
||||
|
||||
return window;
|
||||
}).then(closeWindow).then(loader.unload).then(done, assert.fail);
|
||||
yield closeWindow(window);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
exports["test PBPW Textarea Selection"] = function(assert, done) {
|
||||
let loader = Loader(module);
|
||||
let selection = loader.require("sdk/selection");
|
||||
let window = yield open(URL, { private: true });
|
||||
|
||||
open(URL, {private: true}).then(selectTextarea).then(function(window) {
|
||||
selectTextarea(window);
|
||||
|
||||
assert.equal(selection.isContiguous, false,
|
||||
"selection.isContiguous with Textarea Selection in PBPW works.");
|
||||
assert.equal(selection.isContiguous, false,
|
||||
"selection.isContiguous with Textarea Selection in PBPW works.");
|
||||
|
||||
assert.equal(selection.text, null,
|
||||
"selection.text with Textarea Selection in PBPW works.");
|
||||
assert.equal(selection.text, null,
|
||||
"selection.text with Textarea Selection in PBPW works.");
|
||||
|
||||
assert.strictEqual(selection.html, null,
|
||||
"selection.html with Textarea Selection in PBPW works.");
|
||||
assert.strictEqual(selection.html, null,
|
||||
"selection.html with Textarea Selection in PBPW works.");
|
||||
|
||||
let selectionCount = 0;
|
||||
for each (let sel in selection) {
|
||||
selectionCount++;
|
||||
let selectionCount = 0;
|
||||
for (let sel of selection) {
|
||||
selectionCount++;
|
||||
|
||||
assert.equal(sel.text, null,
|
||||
"iterable selection.text with Textarea Selection in PBPW works.");
|
||||
assert.equal(sel.text, null,
|
||||
"iterable selection.text with Textarea Selection in PBPW works.");
|
||||
|
||||
assert.strictEqual(sel.html, null,
|
||||
"iterable selection.html with Textarea Selection in PBPW works.");
|
||||
}
|
||||
assert.strictEqual(sel.html, null,
|
||||
"iterable selection.html with Textarea Selection in PBPW works.");
|
||||
}
|
||||
|
||||
assert.equal(selectionCount, 0,
|
||||
"No iterable selection in PBPW");
|
||||
assert.equal(selectionCount, 0, "No iterable selection in PBPW");
|
||||
|
||||
return window;
|
||||
}).then(closeWindow).then(loader.unload).then(done, assert.fail);
|
||||
yield closeWindow(window);
|
||||
loader.unload();
|
||||
};
|
||||
|
||||
// TODO: test Selection Listener on long-held connection (Bug 661884)
|
||||
@ -985,7 +970,7 @@ exports["test Selection Listener on long-held connection"] = function(assert, do
|
||||
|
||||
// If the platform doesn't support the PBPW, we're replacing PBPW tests
|
||||
if (!require("sdk/private-browsing/utils").isWindowPBSupported) {
|
||||
Object.keys(module.exports).forEach(function(key) {
|
||||
Object.keys(module.exports).forEach((key) => {
|
||||
if (key.indexOf("test PBPW") === 0) {
|
||||
module.exports[key] = function Unsupported (assert) {
|
||||
assert.pass("Private Window Per Browsing is not supported on this platform.");
|
||||
@ -994,4 +979,4 @@ if (!require("sdk/private-browsing/utils").isWindowPBSupported) {
|
||||
});
|
||||
}
|
||||
|
||||
require("test").run(exports)
|
||||
require("sdk/test").run(exports);
|
||||
|
@ -211,7 +211,7 @@ exports.testActiveWindow = function(assert, done) {
|
||||
let rawWindow2, rawWindow3;
|
||||
|
||||
let testSteps = [
|
||||
function() {
|
||||
() => {
|
||||
assert.equal(windows.length, 3, "Correct number of browser windows");
|
||||
|
||||
let count = 0;
|
||||
@ -223,20 +223,17 @@ exports.testActiveWindow = function(assert, done) {
|
||||
|
||||
continueAfterFocus(rawWindow2);
|
||||
rawWindow2.focus();
|
||||
},
|
||||
function() {
|
||||
}, () => {
|
||||
assert.equal(windows.activeWindow.title, window2.title, "Correct active window - 2");
|
||||
|
||||
continueAfterFocus(rawWindow2);
|
||||
window2.activate();
|
||||
},
|
||||
function() {
|
||||
}, () => {
|
||||
assert.equal(windows.activeWindow.title, window2.title, "Correct active window - 2");
|
||||
|
||||
continueAfterFocus(rawWindow3);
|
||||
window3.activate();
|
||||
},
|
||||
function() {
|
||||
}, () => {
|
||||
assert.equal(windows.activeWindow.title, window3.title, "Correct active window - 3");
|
||||
finishTest();
|
||||
}
|
||||
@ -251,10 +248,10 @@ exports.testActiveWindow = function(assert, done) {
|
||||
|
||||
windows.open({
|
||||
url: "data:text/html;charset=utf-8,<title>window 2</title>",
|
||||
onOpen: function(window) {
|
||||
onOpen: (window) => {
|
||||
assert.pass('window 2 open');
|
||||
|
||||
window.tabs.activeTab.on('ready', function() {
|
||||
window.tabs.activeTab.once('ready', () => {
|
||||
assert.pass('window 2 tab activated');
|
||||
|
||||
window2 = window;
|
||||
@ -267,10 +264,10 @@ exports.testActiveWindow = function(assert, done) {
|
||||
|
||||
windows.open({
|
||||
url: "data:text/html;charset=utf-8,<title>window 3</title>",
|
||||
onOpen: function(window) {
|
||||
onOpen: (window) => {
|
||||
assert.pass('window 3 open');
|
||||
|
||||
window.tabs.activeTab.on('ready', function onReady() {
|
||||
window.tabs.activeTab.once('ready', () => {
|
||||
assert.pass('window 3 tab activated');
|
||||
|
||||
window3 = window;
|
||||
@ -282,7 +279,6 @@ exports.testActiveWindow = function(assert, done) {
|
||||
assert.equal(rawWindow3.document.title, window3.title, "Saw correct title on window 3");
|
||||
|
||||
continueAfterFocus(rawWindow3);
|
||||
rawWindow3.focus();
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -290,24 +286,16 @@ exports.testActiveWindow = function(assert, done) {
|
||||
}
|
||||
});
|
||||
|
||||
function nextStep() {
|
||||
if (testSteps.length) {
|
||||
setTimeout(testSteps.shift())
|
||||
}
|
||||
}
|
||||
|
||||
let continueAfterFocus = function(w) onFocus(w).then(nextStep);
|
||||
let nextStep = () => testSteps.length ? setTimeout(testSteps.shift()) : null;
|
||||
let continueAfterFocus = (w) => onFocus(w).then(nextStep);
|
||||
|
||||
function finishTest() {
|
||||
// close unactive window first to avoid unnecessary focus changing
|
||||
window2.close(function() {
|
||||
window3.close(function() {
|
||||
assert.equal(rawWindow2.closed, true, 'window 2 is closed');
|
||||
assert.equal(rawWindow3.closed, true, 'window 3 is closed');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
window2.close(() => window3.close(() => {
|
||||
assert.equal(rawWindow2.closed, true, 'window 2 is closed');
|
||||
assert.equal(rawWindow3.closed, true, 'window 3 is closed');
|
||||
done();
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="fe92ddd450e03b38edb2d465de7897971d68ac68">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4abb193ddae0f9780ad12ffe5e31772feee3926a"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="8e02f689b0fc39cb6ccdc22d02ed7e219c58faa7"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -19,7 +19,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4abb193ddae0f9780ad12ffe5e31772feee3926a"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e02f689b0fc39cb6ccdc22d02ed7e219c58faa7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4abb193ddae0f9780ad12ffe5e31772feee3926a"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="8e02f689b0fc39cb6ccdc22d02ed7e219c58faa7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="facdb3593e63dcbb740709303a5b2527113c50a0"/>
|
||||
@ -130,8 +130,8 @@
|
||||
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
|
||||
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="197cd9492b9fadaa915c5daf36ff557f8f4a8d1c"/>
|
||||
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
|
||||
<project name="libnfcemu" path="external/libnfcemu" remote="b2g" revision="682e556367e0049bb3ae127cec6e6c459abca1b0"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="57b16fcb790bdf0b53b3c6435a37ccc8ca90ed36"/>
|
||||
<project name="libnfcemu" path="external/libnfcemu" remote="b2g" revision="c7ccf6eff27f99e39a9eca94cde48aaece5e47db"/>
|
||||
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="d259117b4976decbe2f76eeed85218bf0109190f"/>
|
||||
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9f28c4faea3b2f01db227b2467b08aeba96d9bec"/>
|
||||
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="7e5ae3ef2fe1cbc1c9a4fc718b01b7f28b1c5486"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="fe92ddd450e03b38edb2d465de7897971d68ac68">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4abb193ddae0f9780ad12ffe5e31772feee3926a"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="8e02f689b0fc39cb6ccdc22d02ed7e219c58faa7"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -19,7 +19,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4abb193ddae0f9780ad12ffe5e31772feee3926a"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e02f689b0fc39cb6ccdc22d02ed7e219c58faa7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<project name="platform_build" path="build" remote="b2g" revision="fe92ddd450e03b38edb2d465de7897971d68ac68">
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4abb193ddae0f9780ad12ffe5e31772feee3926a"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="8e02f689b0fc39cb6ccdc22d02ed7e219c58faa7"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
</project>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4abb193ddae0f9780ad12ffe5e31772feee3926a"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="8e02f689b0fc39cb6ccdc22d02ed7e219c58faa7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="facdb3593e63dcbb740709303a5b2527113c50a0"/>
|
||||
|
@ -4,6 +4,6 @@
|
||||
"remote": "",
|
||||
"branch": ""
|
||||
},
|
||||
"revision": "d43b2e2dba496f624d62c1b72a22dc5e34953fbd",
|
||||
"revision": "7c8bbd0eb4543c729b029339cb79bfaa4ec23069",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4abb193ddae0f9780ad12ffe5e31772feee3926a"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e02f689b0fc39cb6ccdc22d02ed7e219c58faa7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -15,7 +15,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4abb193ddae0f9780ad12ffe5e31772feee3926a"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e02f689b0fc39cb6ccdc22d02ed7e219c58faa7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
</project>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="4abb193ddae0f9780ad12ffe5e31772feee3926a"/>
|
||||
<project name="gaia" path="gaia" remote="mozillaorg" revision="8e02f689b0fc39cb6ccdc22d02ed7e219c58faa7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
|
||||
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="facdb3593e63dcbb740709303a5b2527113c50a0"/>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<copyfile dest="Makefile" src="core/root.mk"/>
|
||||
</project>
|
||||
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="4abb193ddae0f9780ad12ffe5e31772feee3926a"/>
|
||||
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e02f689b0fc39cb6ccdc22d02ed7e219c58faa7"/>
|
||||
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="6969df171e5295f855f12d12db0382048e6892e7"/>
|
||||
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
|
||||
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
|
||||
|
@ -298,16 +298,28 @@ function ensureSnippetsMapThen(aCallback)
|
||||
|
||||
function onSearchSubmit(aEvent)
|
||||
{
|
||||
let searchTerms = document.getElementById("searchText").value;
|
||||
let searchText = document.getElementById("searchText");
|
||||
let searchTerms = searchText.value;
|
||||
let engineName = document.documentElement.getAttribute("searchEngineName");
|
||||
|
||||
if (engineName && searchTerms.length > 0) {
|
||||
// Send an event that will perform a search and Firefox Health Report will
|
||||
// record that a search from about:home has occurred.
|
||||
let eventData = JSON.stringify({
|
||||
|
||||
let eventData = {
|
||||
engineName: engineName,
|
||||
searchTerms: searchTerms
|
||||
});
|
||||
};
|
||||
|
||||
if (searchText.hasAttribute("selection-index")) {
|
||||
eventData.selection = {
|
||||
index: searchText.getAttribute("selection-index"),
|
||||
kind: searchText.getAttribute("selection-kind")
|
||||
};
|
||||
}
|
||||
|
||||
eventData = JSON.stringify(eventData);
|
||||
|
||||
let event = new CustomEvent("AboutHomeSearchEvent", {detail: eventData});
|
||||
document.dispatchEvent(event);
|
||||
}
|
||||
|
@ -3125,9 +3125,13 @@ const BrowserSearch = {
|
||||
* @param source
|
||||
* (string) Where the search originated from. See the FHR
|
||||
* SearchesProvider for allowed values.
|
||||
* @param selection [optional]
|
||||
* ({index: The selected index, kind: "key" or "mouse"}) If
|
||||
* the search was a suggested search, this indicates where the
|
||||
* item was in the suggestion list and how the user selected it.
|
||||
*/
|
||||
recordSearchInHealthReport: function (engine, source) {
|
||||
BrowserUITelemetry.countSearchEvent(source);
|
||||
recordSearchInHealthReport: function (engine, source, selection) {
|
||||
BrowserUITelemetry.countSearchEvent(source, null, selection);
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
let reporter = Cc["@mozilla.org/datareporting/service;1"]
|
||||
.getService()
|
||||
|
@ -33,13 +33,24 @@ let gSearch = {
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
let searchStr = this._nodes.text.value;
|
||||
let searchText = this._nodes.text;
|
||||
let searchStr = searchText.value;
|
||||
if (this.currentEngineName && searchStr.length) {
|
||||
this._send("Search", {
|
||||
|
||||
let eventData = {
|
||||
engineName: this.currentEngineName,
|
||||
searchString: searchStr,
|
||||
whence: "newtab",
|
||||
});
|
||||
}
|
||||
|
||||
if (searchText.hasAttribute("selection-index")) {
|
||||
eventData.selection = {
|
||||
index: searchText.getAttribute("selection-index"),
|
||||
kind: searchText.getAttribute("selection-kind")
|
||||
};
|
||||
}
|
||||
|
||||
this._send("Search", eventData);
|
||||
}
|
||||
this._suggestionController.addInputValueToFormHistory();
|
||||
},
|
||||
|
@ -179,6 +179,13 @@ SearchSuggestionUIController.prototype = {
|
||||
case event.DOM_VK_RETURN:
|
||||
if (this.selectedIndex >= 0) {
|
||||
this.input.value = this.suggestionAtIndex(this.selectedIndex);
|
||||
this.input.setAttribute("selection-index", this.selectedIndex);
|
||||
this.input.setAttribute("selection-kind", "key");
|
||||
} else {
|
||||
// If we didn't select anything, make sure to remove the attributes
|
||||
// in case they were populated last time.
|
||||
this.input.removeAttribute("selection-index");
|
||||
this.input.removeAttribute("selection-kind");
|
||||
}
|
||||
this._stickyInputValue = this.input.value;
|
||||
this._hideSuggestions();
|
||||
@ -228,6 +235,8 @@ SearchSuggestionUIController.prototype = {
|
||||
let suggestion = this.suggestionAtIndex(idx);
|
||||
this._stickyInputValue = suggestion;
|
||||
this.input.value = suggestion;
|
||||
this.input.setAttribute("selection-index", idx);
|
||||
this.input.setAttribute("selection-kind", "mouse");
|
||||
this._hideSuggestions();
|
||||
if (this.onClick) {
|
||||
this.onClick.call(null);
|
||||
|
@ -151,7 +151,7 @@ loop.conversation = (function(OT, mozL10n) {
|
||||
*
|
||||
* Required options:
|
||||
* - {loop.shared.models.ConversationModel} conversation Conversation model.
|
||||
* - {loop.shared.components.Notifier} notifier Notifier component.
|
||||
* - {loop.shared.models.NotificationCollection} notifications
|
||||
*
|
||||
* @type {loop.shared.router.BaseConversationRouter}
|
||||
*/
|
||||
@ -206,7 +206,7 @@ loop.conversation = (function(OT, mozL10n) {
|
||||
console.error("Failed to get the sessionData", err);
|
||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||
// this by better "call failed" UI.
|
||||
this._notifier.errorL10n("cannot_start_call_session_not_ready");
|
||||
this._notifications.errorL10n("cannot_start_call_session_not_ready");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -331,7 +331,7 @@ loop.conversation = (function(OT, mozL10n) {
|
||||
_handleSessionError: function() {
|
||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||
// this by better "call failed" UI.
|
||||
this._notifier.errorL10n("cannot_start_call_session_not_ready");
|
||||
this._notifications.errorL10n("cannot_start_call_session_not_ready");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -376,7 +376,7 @@ loop.conversation = (function(OT, mozL10n) {
|
||||
conversation: new loop.shared.models.ConversationModel(
|
||||
{}, // Model attributes
|
||||
{sdk: OT}), // Model dependencies
|
||||
notifier: new sharedViews.NotificationListView({el: "#messages"})
|
||||
notifications: new loop.shared.models.NotificationCollection()
|
||||
});
|
||||
Backbone.history.start();
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ loop.conversation = (function(OT, mozL10n) {
|
||||
*
|
||||
* Required options:
|
||||
* - {loop.shared.models.ConversationModel} conversation Conversation model.
|
||||
* - {loop.shared.components.Notifier} notifier Notifier component.
|
||||
* - {loop.shared.models.NotificationCollection} notifications
|
||||
*
|
||||
* @type {loop.shared.router.BaseConversationRouter}
|
||||
*/
|
||||
@ -206,7 +206,7 @@ loop.conversation = (function(OT, mozL10n) {
|
||||
console.error("Failed to get the sessionData", err);
|
||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||
// this by better "call failed" UI.
|
||||
this._notifier.errorL10n("cannot_start_call_session_not_ready");
|
||||
this._notifications.errorL10n("cannot_start_call_session_not_ready");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -331,7 +331,7 @@ loop.conversation = (function(OT, mozL10n) {
|
||||
_handleSessionError: function() {
|
||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||
// this by better "call failed" UI.
|
||||
this._notifier.errorL10n("cannot_start_call_session_not_ready");
|
||||
this._notifications.errorL10n("cannot_start_call_session_not_ready");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -376,7 +376,7 @@ loop.conversation = (function(OT, mozL10n) {
|
||||
conversation: new loop.shared.models.ConversationModel(
|
||||
{}, // Model attributes
|
||||
{sdk: OT}), // Model dependencies
|
||||
notifier: new sharedViews.NotificationListView({el: "#messages"})
|
||||
notifications: new loop.shared.models.NotificationCollection()
|
||||
});
|
||||
Backbone.history.start();
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedViews = loop.shared.views,
|
||||
sharedModels = loop.shared.models,
|
||||
// aliasing translation function as __ for concision
|
||||
__ = mozL10n.get;
|
||||
|
||||
@ -261,7 +262,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
propTypes: {
|
||||
callUrl: React.PropTypes.string,
|
||||
callUrlExpiry: React.PropTypes.number,
|
||||
notifier: React.PropTypes.object.isRequired,
|
||||
notifications: React.PropTypes.object.isRequired,
|
||||
client: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
@ -296,10 +297,10 @@ loop.panel = (function(_, mozL10n) {
|
||||
},
|
||||
|
||||
_onCallUrlReceived: function(err, callUrlData) {
|
||||
this.props.notifier.clear();
|
||||
this.props.notifications.reset();
|
||||
|
||||
if (err) {
|
||||
this.props.notifier.errorL10n("unable_retrieve_url");
|
||||
this.props.notifications.errorL10n("unable_retrieve_url");
|
||||
this.setState(this.getInitialState());
|
||||
} else {
|
||||
try {
|
||||
@ -314,7 +315,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
callUrlExpiry: callUrlData.expiresAt});
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
this.props.notifier.errorL10n("unable_retrieve_url");
|
||||
this.props.notifications.errorL10n("unable_retrieve_url");
|
||||
this.setState(this.getInitialState());
|
||||
}
|
||||
}
|
||||
@ -411,17 +412,20 @@ loop.panel = (function(_, mozL10n) {
|
||||
*/
|
||||
var PanelView = React.createClass({displayName: 'PanelView',
|
||||
propTypes: {
|
||||
notifier: React.PropTypes.object.isRequired,
|
||||
notifications: React.PropTypes.object.isRequired,
|
||||
client: React.PropTypes.object.isRequired,
|
||||
// Mostly used for UI components showcase and unit tests
|
||||
callUrl: React.PropTypes.string
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var NotificationListView = sharedViews.NotificationListView;
|
||||
|
||||
return (
|
||||
React.DOM.div(null,
|
||||
NotificationListView({notifications: this.props.notifications}),
|
||||
CallUrlResult({client: this.props.client,
|
||||
notifier: this.props.notifier,
|
||||
notifications: this.props.notifications,
|
||||
callUrl: this.props.callUrl}),
|
||||
ToSView(null),
|
||||
React.DOM.div({className: "footer"},
|
||||
@ -454,7 +458,6 @@ loop.panel = (function(_, mozL10n) {
|
||||
|
||||
this._registerVisibilityChangeEvent();
|
||||
|
||||
this.on("panel:open panel:closed", this.clearNotifications, this);
|
||||
this.on("panel:open", this.reset, this);
|
||||
},
|
||||
|
||||
@ -483,20 +486,16 @@ loop.panel = (function(_, mozL10n) {
|
||||
this.reset();
|
||||
},
|
||||
|
||||
clearNotifications: function() {
|
||||
this._notifier.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets this router to its initial state.
|
||||
*/
|
||||
reset: function() {
|
||||
this._notifier.clear();
|
||||
this._notifications.reset();
|
||||
var client = new loop.Client({
|
||||
baseServerUrl: navigator.mozLoop.serverUrl
|
||||
});
|
||||
this.loadReactComponent(PanelView({client: client,
|
||||
notifier: this._notifier}));
|
||||
this.loadReactComponent(
|
||||
PanelView({client: client, notifications: this._notifications}));
|
||||
}
|
||||
});
|
||||
|
||||
@ -510,7 +509,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
|
||||
router = new PanelRouter({
|
||||
document: document,
|
||||
notifier: new sharedViews.NotificationListView({el: "#messages"})
|
||||
notifications: new sharedModels.NotificationCollection()
|
||||
});
|
||||
Backbone.history.start();
|
||||
|
||||
|
@ -12,6 +12,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
"use strict";
|
||||
|
||||
var sharedViews = loop.shared.views,
|
||||
sharedModels = loop.shared.models,
|
||||
// aliasing translation function as __ for concision
|
||||
__ = mozL10n.get;
|
||||
|
||||
@ -261,7 +262,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
propTypes: {
|
||||
callUrl: React.PropTypes.string,
|
||||
callUrlExpiry: React.PropTypes.number,
|
||||
notifier: React.PropTypes.object.isRequired,
|
||||
notifications: React.PropTypes.object.isRequired,
|
||||
client: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
@ -296,10 +297,10 @@ loop.panel = (function(_, mozL10n) {
|
||||
},
|
||||
|
||||
_onCallUrlReceived: function(err, callUrlData) {
|
||||
this.props.notifier.clear();
|
||||
this.props.notifications.reset();
|
||||
|
||||
if (err) {
|
||||
this.props.notifier.errorL10n("unable_retrieve_url");
|
||||
this.props.notifications.errorL10n("unable_retrieve_url");
|
||||
this.setState(this.getInitialState());
|
||||
} else {
|
||||
try {
|
||||
@ -314,7 +315,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
callUrlExpiry: callUrlData.expiresAt});
|
||||
} catch(e) {
|
||||
console.log(e);
|
||||
this.props.notifier.errorL10n("unable_retrieve_url");
|
||||
this.props.notifications.errorL10n("unable_retrieve_url");
|
||||
this.setState(this.getInitialState());
|
||||
}
|
||||
}
|
||||
@ -411,17 +412,20 @@ loop.panel = (function(_, mozL10n) {
|
||||
*/
|
||||
var PanelView = React.createClass({
|
||||
propTypes: {
|
||||
notifier: React.PropTypes.object.isRequired,
|
||||
notifications: React.PropTypes.object.isRequired,
|
||||
client: React.PropTypes.object.isRequired,
|
||||
// Mostly used for UI components showcase and unit tests
|
||||
callUrl: React.PropTypes.string
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var NotificationListView = sharedViews.NotificationListView;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NotificationListView notifications={this.props.notifications} />
|
||||
<CallUrlResult client={this.props.client}
|
||||
notifier={this.props.notifier}
|
||||
notifications={this.props.notifications}
|
||||
callUrl={this.props.callUrl} />
|
||||
<ToSView />
|
||||
<div className="footer">
|
||||
@ -454,7 +458,6 @@ loop.panel = (function(_, mozL10n) {
|
||||
|
||||
this._registerVisibilityChangeEvent();
|
||||
|
||||
this.on("panel:open panel:closed", this.clearNotifications, this);
|
||||
this.on("panel:open", this.reset, this);
|
||||
},
|
||||
|
||||
@ -483,20 +486,16 @@ loop.panel = (function(_, mozL10n) {
|
||||
this.reset();
|
||||
},
|
||||
|
||||
clearNotifications: function() {
|
||||
this._notifier.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets this router to its initial state.
|
||||
*/
|
||||
reset: function() {
|
||||
this._notifier.clear();
|
||||
this._notifications.reset();
|
||||
var client = new loop.Client({
|
||||
baseServerUrl: navigator.mozLoop.serverUrl
|
||||
});
|
||||
this.loadReactComponent(<PanelView client={client}
|
||||
notifier={this._notifier} />);
|
||||
this.loadReactComponent(
|
||||
<PanelView client={client} notifications={this._notifications}/>);
|
||||
}
|
||||
});
|
||||
|
||||
@ -510,7 +509,7 @@ loop.panel = (function(_, mozL10n) {
|
||||
|
||||
router = new PanelRouter({
|
||||
document: document,
|
||||
notifier: new sharedViews.NotificationListView({el: "#messages"})
|
||||
notifications: new sharedModels.NotificationCollection()
|
||||
});
|
||||
Backbone.history.start();
|
||||
|
||||
|
@ -224,6 +224,7 @@ p {
|
||||
background: #eee;
|
||||
padding: .2em 1em;
|
||||
margin-bottom: 1em;
|
||||
border-bottom: 2px solid #E9E9E9;
|
||||
}
|
||||
|
||||
.alert p.message {
|
||||
@ -232,8 +233,13 @@ p {
|
||||
}
|
||||
|
||||
.alert.alert-error {
|
||||
background: #f99;
|
||||
border: 1px solid #f77;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
padding: 5px;
|
||||
font-size: 10px;
|
||||
justify-content: center;
|
||||
color: #FFF;
|
||||
background: repeating-linear-gradient(-45deg, #D74345, #D74345 10px, #D94B4D 10px, #D94B4D 20px) repeat scroll 0% 0% transparent;
|
||||
}
|
||||
|
||||
.alert.alert-warning {
|
||||
|
@ -393,15 +393,14 @@
|
||||
background-image: url("../img/sad.png");
|
||||
}
|
||||
|
||||
.feedback button.back {
|
||||
.fx-embedded-btn-back {
|
||||
margin-bottom: 1rem;
|
||||
padding: .2rem .8rem;
|
||||
border: 1px solid #aaa;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #CCC;
|
||||
color: #CCC;
|
||||
font-size: 11px;
|
||||
background: transparent;
|
||||
color: #777;
|
||||
cursor: pointer;
|
||||
padding: 3px 10px;
|
||||
display: inline;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.feedback label {
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
var loop = loop || {};
|
||||
loop.shared = loop.shared || {};
|
||||
loop.shared.models = (function() {
|
||||
loop.shared.models = (function(l10n) {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
@ -375,7 +375,43 @@ loop.shared.models = (function() {
|
||||
* Notification collection
|
||||
*/
|
||||
var NotificationCollection = Backbone.Collection.extend({
|
||||
model: NotificationModel
|
||||
model: NotificationModel,
|
||||
|
||||
/**
|
||||
* Adds a warning notification to the stack and renders it.
|
||||
*
|
||||
* @return {String} message
|
||||
*/
|
||||
warn: function(message) {
|
||||
this.add({level: "warning", message: message});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a l10n warning notification to the stack and renders it.
|
||||
*
|
||||
* @param {String} messageId L10n message id
|
||||
*/
|
||||
warnL10n: function(messageId) {
|
||||
this.warn(l10n.get(messageId));
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an error notification to the stack and renders it.
|
||||
*
|
||||
* @return {String} message
|
||||
*/
|
||||
error: function(message) {
|
||||
this.add({level: "error", message: message});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a l10n rror notification to the stack and renders it.
|
||||
*
|
||||
* @param {String} messageId L10n message id
|
||||
*/
|
||||
errorL10n: function(messageId) {
|
||||
this.error(l10n.get(messageId));
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
@ -383,4 +419,4 @@ loop.shared.models = (function() {
|
||||
NotificationCollection: NotificationCollection,
|
||||
NotificationModel: NotificationModel
|
||||
};
|
||||
})();
|
||||
})(navigator.mozL10n || document.mozL10n);
|
||||
|
@ -23,25 +23,25 @@ loop.shared.router = (function() {
|
||||
_activeView: undefined,
|
||||
|
||||
/**
|
||||
* Notifications dispatcher.
|
||||
* @type {loop.shared.views.NotificationListView}
|
||||
* Notifications collection.
|
||||
* @type {loop.shared.models.NotificationCollection}
|
||||
*/
|
||||
_notifier: undefined,
|
||||
_notifications: undefined,
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Required options:
|
||||
* - {loop.shared.views.NotificationListView} notifier Notifier view.
|
||||
* - {loop.shared.models.NotificationCollection} notifications
|
||||
*
|
||||
* @param {Object} options Options object.
|
||||
*/
|
||||
constructor: function(options) {
|
||||
options = options || {};
|
||||
if (!options.notifier) {
|
||||
throw new Error("missing required notifier");
|
||||
if (!options.notifications) {
|
||||
throw new Error("missing required notifications");
|
||||
}
|
||||
this._notifier = options.notifier;
|
||||
this._notifications = options.notifications;
|
||||
|
||||
Backbone.Router.apply(this, arguments);
|
||||
},
|
||||
@ -144,7 +144,7 @@ loop.shared.router = (function() {
|
||||
*/
|
||||
_notifyError: function(error) {
|
||||
console.log(error);
|
||||
this._notifier.errorL10n("connection_error_see_console_notification");
|
||||
this._notifications.errorL10n("connection_error_see_console_notification");
|
||||
this.endCall();
|
||||
},
|
||||
|
||||
@ -169,7 +169,7 @@ loop.shared.router = (function() {
|
||||
* @param {Object} event
|
||||
*/
|
||||
_onPeerHungup: function() {
|
||||
this._notifier.warnL10n("peer_ended_conversation2");
|
||||
this._notifications.warnL10n("peer_ended_conversation2");
|
||||
this.endCall();
|
||||
},
|
||||
|
||||
@ -177,7 +177,7 @@ loop.shared.router = (function() {
|
||||
* Network disconnected. Notifies the user and ends the call.
|
||||
*/
|
||||
_onNetworkDisconnected: function() {
|
||||
this._notifier.warnL10n("network_disconnected");
|
||||
this._notifications.warnL10n("network_disconnected");
|
||||
this.endCall();
|
||||
}
|
||||
});
|
||||
|
@ -407,7 +407,8 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
var backButton = React.DOM.div(null);
|
||||
if (this.props.reset) {
|
||||
backButton = (
|
||||
React.DOM.button({className: "back", type: "button", onClick: this.props.reset},
|
||||
React.DOM.button({className: "fx-embedded-btn-back", type: "button",
|
||||
onClick: this.props.reset},
|
||||
"« ", l10n.get("feedback_back_button")
|
||||
)
|
||||
);
|
||||
@ -651,133 +652,55 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
/**
|
||||
* Notification view.
|
||||
*/
|
||||
var NotificationView = BaseView.extend({
|
||||
template: _.template([
|
||||
'<div class="alert alert-<%- level %>">',
|
||||
' <button class="close"></button>',
|
||||
' <p class="message"><%- message %></p>',
|
||||
'</div>'
|
||||
].join("")),
|
||||
var NotificationView = React.createClass({
|
||||
displayName: 'NotificationView',
|
||||
mixins: [Backbone.Events],
|
||||
|
||||
events: {
|
||||
"click .close": "dismiss"
|
||||
},
|
||||
|
||||
dismiss: function(event) {
|
||||
event.preventDefault();
|
||||
this.$el.addClass("fade-out");
|
||||
setTimeout(function() {
|
||||
this.collection.remove(this.model);
|
||||
this.remove();
|
||||
}.bind(this), 500); // XXX make timeout value configurable
|
||||
propTypes: {
|
||||
notification: React.PropTypes.object.isRequired,
|
||||
key: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.template(this.model.toJSON()));
|
||||
return this;
|
||||
var notification = this.props.notification;
|
||||
return (
|
||||
React.DOM.div({key: this.props.key,
|
||||
className: "alert alert-" + notification.get("level")},
|
||||
React.DOM.span({className: "message"}, notification.get("message"))
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Notification list view.
|
||||
*/
|
||||
var NotificationListView = Backbone.View.extend({
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Available options:
|
||||
* - {loop.shared.models.NotificationCollection} collection Notifications
|
||||
* collection
|
||||
*
|
||||
* @param {Object} options Options object
|
||||
*/
|
||||
initialize: function(options) {
|
||||
options = options || {};
|
||||
if (!options.collection) {
|
||||
this.collection = new sharedModels.NotificationCollection();
|
||||
}
|
||||
this.listenTo(this.collection, "reset add remove", this.render);
|
||||
var NotificationListView = React.createClass({displayName: 'NotificationListView',
|
||||
mixins: [Backbone.Events],
|
||||
|
||||
propTypes: {
|
||||
notifications: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the notification stack.
|
||||
*/
|
||||
clear: function() {
|
||||
this.collection.reset();
|
||||
componentDidMount: function() {
|
||||
this.listenTo(this.props.notifications, "reset add remove", function() {
|
||||
this.forceUpdate();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new notification to the stack, triggering rendering of it.
|
||||
*
|
||||
* @param {Object|NotificationModel} notification Notification data.
|
||||
*/
|
||||
notify: function(notification) {
|
||||
this.collection.add(notification);
|
||||
componentWillUnmount: function() {
|
||||
this.stopListening(this.props.notifications);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new notification to the stack using an l10n message identifier,
|
||||
* triggering rendering of it.
|
||||
*
|
||||
* @param {String} messageId L10n message id
|
||||
* @param {String} level Notification level
|
||||
*/
|
||||
notifyL10n: function(messageId, level) {
|
||||
this.notify({
|
||||
message: l10n.get(messageId),
|
||||
level: level
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a warning notification to the stack and renders it.
|
||||
*
|
||||
* @return {String} message
|
||||
*/
|
||||
warn: function(message) {
|
||||
this.notify({level: "warning", message: message});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a l10n warning notification to the stack and renders it.
|
||||
*
|
||||
* @param {String} messageId L10n message id
|
||||
*/
|
||||
warnL10n: function(messageId) {
|
||||
this.warn(l10n.get(messageId));
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an error notification to the stack and renders it.
|
||||
*
|
||||
* @return {String} message
|
||||
*/
|
||||
error: function(message) {
|
||||
this.notify({level: "error", message: message});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a l10n rror notification to the stack and renders it.
|
||||
*
|
||||
* @param {String} messageId L10n message id
|
||||
*/
|
||||
errorL10n: function(messageId) {
|
||||
this.error(l10n.get(messageId));
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders this view.
|
||||
*
|
||||
* @return {loop.shared.views.NotificationListView}
|
||||
*/
|
||||
render: function() {
|
||||
this.$el.html(this.collection.map(function(notification) {
|
||||
return new NotificationView({
|
||||
model: notification,
|
||||
collection: this.collection
|
||||
}).render().$el;
|
||||
}.bind(this)));
|
||||
return this;
|
||||
return (
|
||||
React.DOM.div({id: "messages"},
|
||||
this.props.notifications.map(function(notification, key) {
|
||||
return NotificationView({key: key, notification: notification});
|
||||
})
|
||||
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -817,7 +740,6 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
FeedbackView: FeedbackView,
|
||||
MediaControlButton: MediaControlButton,
|
||||
NotificationListView: NotificationListView,
|
||||
NotificationView: NotificationView,
|
||||
UnsupportedBrowserView: UnsupportedBrowserView,
|
||||
UnsupportedDeviceView: UnsupportedDeviceView
|
||||
};
|
||||
|
@ -407,7 +407,8 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
var backButton = <div />;
|
||||
if (this.props.reset) {
|
||||
backButton = (
|
||||
<button className="back" type="button" onClick={this.props.reset}>
|
||||
<button className="fx-embedded-btn-back" type="button"
|
||||
onClick={this.props.reset}>
|
||||
« {l10n.get("feedback_back_button")}
|
||||
</button>
|
||||
);
|
||||
@ -651,133 +652,55 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
/**
|
||||
* Notification view.
|
||||
*/
|
||||
var NotificationView = BaseView.extend({
|
||||
template: _.template([
|
||||
'<div class="alert alert-<%- level %>">',
|
||||
' <button class="close"></button>',
|
||||
' <p class="message"><%- message %></p>',
|
||||
'</div>'
|
||||
].join("")),
|
||||
var NotificationView = React.createClass({
|
||||
displayName: 'NotificationView',
|
||||
mixins: [Backbone.Events],
|
||||
|
||||
events: {
|
||||
"click .close": "dismiss"
|
||||
},
|
||||
|
||||
dismiss: function(event) {
|
||||
event.preventDefault();
|
||||
this.$el.addClass("fade-out");
|
||||
setTimeout(function() {
|
||||
this.collection.remove(this.model);
|
||||
this.remove();
|
||||
}.bind(this), 500); // XXX make timeout value configurable
|
||||
propTypes: {
|
||||
notification: React.PropTypes.object.isRequired,
|
||||
key: React.PropTypes.number.isRequired
|
||||
},
|
||||
|
||||
render: function() {
|
||||
this.$el.html(this.template(this.model.toJSON()));
|
||||
return this;
|
||||
var notification = this.props.notification;
|
||||
return (
|
||||
<div key={this.props.key}
|
||||
className={"alert alert-" + notification.get("level")}>
|
||||
<span className="message">{notification.get("message")}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Notification list view.
|
||||
*/
|
||||
var NotificationListView = Backbone.View.extend({
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* Available options:
|
||||
* - {loop.shared.models.NotificationCollection} collection Notifications
|
||||
* collection
|
||||
*
|
||||
* @param {Object} options Options object
|
||||
*/
|
||||
initialize: function(options) {
|
||||
options = options || {};
|
||||
if (!options.collection) {
|
||||
this.collection = new sharedModels.NotificationCollection();
|
||||
}
|
||||
this.listenTo(this.collection, "reset add remove", this.render);
|
||||
var NotificationListView = React.createClass({
|
||||
mixins: [Backbone.Events],
|
||||
|
||||
propTypes: {
|
||||
notifications: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the notification stack.
|
||||
*/
|
||||
clear: function() {
|
||||
this.collection.reset();
|
||||
componentDidMount: function() {
|
||||
this.listenTo(this.props.notifications, "reset add remove", function() {
|
||||
this.forceUpdate();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new notification to the stack, triggering rendering of it.
|
||||
*
|
||||
* @param {Object|NotificationModel} notification Notification data.
|
||||
*/
|
||||
notify: function(notification) {
|
||||
this.collection.add(notification);
|
||||
componentWillUnmount: function() {
|
||||
this.stopListening(this.props.notifications);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new notification to the stack using an l10n message identifier,
|
||||
* triggering rendering of it.
|
||||
*
|
||||
* @param {String} messageId L10n message id
|
||||
* @param {String} level Notification level
|
||||
*/
|
||||
notifyL10n: function(messageId, level) {
|
||||
this.notify({
|
||||
message: l10n.get(messageId),
|
||||
level: level
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a warning notification to the stack and renders it.
|
||||
*
|
||||
* @return {String} message
|
||||
*/
|
||||
warn: function(message) {
|
||||
this.notify({level: "warning", message: message});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a l10n warning notification to the stack and renders it.
|
||||
*
|
||||
* @param {String} messageId L10n message id
|
||||
*/
|
||||
warnL10n: function(messageId) {
|
||||
this.warn(l10n.get(messageId));
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an error notification to the stack and renders it.
|
||||
*
|
||||
* @return {String} message
|
||||
*/
|
||||
error: function(message) {
|
||||
this.notify({level: "error", message: message});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a l10n rror notification to the stack and renders it.
|
||||
*
|
||||
* @param {String} messageId L10n message id
|
||||
*/
|
||||
errorL10n: function(messageId) {
|
||||
this.error(l10n.get(messageId));
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders this view.
|
||||
*
|
||||
* @return {loop.shared.views.NotificationListView}
|
||||
*/
|
||||
render: function() {
|
||||
this.$el.html(this.collection.map(function(notification) {
|
||||
return new NotificationView({
|
||||
model: notification,
|
||||
collection: this.collection
|
||||
}).render().$el;
|
||||
}.bind(this)));
|
||||
return this;
|
||||
return (
|
||||
<div id="messages">{
|
||||
this.props.notifications.map(function(notification, key) {
|
||||
return <NotificationView key={key} notification={notification}/>;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -817,7 +740,6 @@ loop.shared.views = (function(_, OT, l10n) {
|
||||
FeedbackView: FeedbackView,
|
||||
MediaControlButton: MediaControlButton,
|
||||
NotificationListView: NotificationListView,
|
||||
NotificationView: NotificationView,
|
||||
UnsupportedBrowserView: UnsupportedBrowserView,
|
||||
UnsupportedDeviceView: UnsupportedDeviceView
|
||||
};
|
||||
|
@ -135,8 +135,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
* Constructor.
|
||||
*
|
||||
* Required options:
|
||||
* - {loop.shared.model.ConversationModel} model Conversation model.
|
||||
* - {loop.shared.views.NotificationListView} notifier Notifier component.
|
||||
* - {loop.shared.models.ConversationModel} model Conversation model.
|
||||
* - {loop.shared.models.NotificationCollection} notifications
|
||||
*
|
||||
*/
|
||||
|
||||
@ -156,7 +156,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
model: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
.isRequired,
|
||||
// XXX Check more tightly here when we start injecting window.loop.*
|
||||
notifier: React.PropTypes.object.isRequired,
|
||||
notifications: React.PropTypes.object.isRequired,
|
||||
client: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
@ -167,14 +167,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
this._onSessionError);
|
||||
this.props.client.requestCallUrlInfo(this.props.model.get("loopToken"),
|
||||
this._setConversationTimestamp);
|
||||
// XXX DOM element does not exist before React view gets instantiated
|
||||
// We should turn the notifier into a react component
|
||||
this.props.notifier.$el = $("#messages");
|
||||
},
|
||||
|
||||
_onSessionError: function(error) {
|
||||
console.error(error);
|
||||
this.props.notifier.errorL10n("unable_retrieve_call_info");
|
||||
this.props.notifications.errorL10n("unable_retrieve_call_info");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -195,7 +192,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
|
||||
_setConversationTimestamp: function(err, callUrlInfo) {
|
||||
if (err) {
|
||||
this.props.notifier.errorL10n("unable_retrieve_call_info");
|
||||
this.props.notifications.errorL10n("unable_retrieve_call_info");
|
||||
} else {
|
||||
var date = (new Date(callUrlInfo.urlCreationDate * 1000));
|
||||
var options = {year: "numeric", month: "long", day: "numeric"};
|
||||
@ -343,7 +340,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
setupOutgoingCall: function() {
|
||||
var loopToken = this._conversation.get("loopToken");
|
||||
if (!loopToken) {
|
||||
this._notifier.errorL10n("missing_conversation_info");
|
||||
this._notifications.errorL10n("missing_conversation_info");
|
||||
this.navigate("home", {trigger: true});
|
||||
} else {
|
||||
var callType = this._conversation.get("selectedCallType");
|
||||
@ -361,7 +358,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
this._onSessionExpired();
|
||||
break;
|
||||
default:
|
||||
this._notifier.errorL10n("missing_conversation_info");
|
||||
this._notifications.errorL10n("missing_conversation_info");
|
||||
this.navigate("home", {trigger: true});
|
||||
break;
|
||||
}
|
||||
@ -378,7 +375,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
startCall: function() {
|
||||
var loopToken = this._conversation.get("loopToken");
|
||||
if (!loopToken) {
|
||||
this._notifier.errorL10n("missing_conversation_info");
|
||||
this._notifications.errorL10n("missing_conversation_info");
|
||||
this.navigate("home", {trigger: true});
|
||||
} else {
|
||||
this._setupWebSocketAndCallView(loopToken);
|
||||
@ -404,7 +401,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
}.bind(this), function() {
|
||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||
// this by better "call failed" UI.
|
||||
this._notifier.errorL10n("cannot_start_call_session_not_ready");
|
||||
this._notifications.errorL10n("cannot_start_call_session_not_ready");
|
||||
return;
|
||||
}.bind(this));
|
||||
|
||||
@ -451,7 +448,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
*/
|
||||
_handleCallRejected: function() {
|
||||
this.endCall();
|
||||
this._notifier.errorL10n("call_timeout_notification_text");
|
||||
this._notifications.errorL10n("call_timeout_notification_text");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -466,7 +463,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
},
|
||||
|
||||
_onTimeout: function() {
|
||||
this._notifier.errorL10n("call_timeout_notification_text");
|
||||
this._notifications.errorL10n("call_timeout_notification_text");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -504,7 +501,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
|
||||
var startView = StartConversationView({
|
||||
model: this._conversation,
|
||||
notifier: this._notifier,
|
||||
notifications: this._notifications,
|
||||
client: this._client
|
||||
});
|
||||
this._conversation.once("call:outgoing:setup", this.setupOutgoingCall, this);
|
||||
@ -557,7 +554,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
});
|
||||
var router = new WebappRouter({
|
||||
helper: helper,
|
||||
notifier: new sharedViews.NotificationListView({el: "#messages"}),
|
||||
notifications: new sharedModels.NotificationCollection(),
|
||||
client: client,
|
||||
conversation: new sharedModels.ConversationModel({}, {
|
||||
sdk: OT,
|
||||
|
@ -135,8 +135,8 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
* Constructor.
|
||||
*
|
||||
* Required options:
|
||||
* - {loop.shared.model.ConversationModel} model Conversation model.
|
||||
* - {loop.shared.views.NotificationListView} notifier Notifier component.
|
||||
* - {loop.shared.models.ConversationModel} model Conversation model.
|
||||
* - {loop.shared.models.NotificationCollection} notifications
|
||||
*
|
||||
*/
|
||||
|
||||
@ -156,7 +156,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
model: React.PropTypes.instanceOf(sharedModels.ConversationModel)
|
||||
.isRequired,
|
||||
// XXX Check more tightly here when we start injecting window.loop.*
|
||||
notifier: React.PropTypes.object.isRequired,
|
||||
notifications: React.PropTypes.object.isRequired,
|
||||
client: React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
@ -167,14 +167,11 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
this._onSessionError);
|
||||
this.props.client.requestCallUrlInfo(this.props.model.get("loopToken"),
|
||||
this._setConversationTimestamp);
|
||||
// XXX DOM element does not exist before React view gets instantiated
|
||||
// We should turn the notifier into a react component
|
||||
this.props.notifier.$el = $("#messages");
|
||||
},
|
||||
|
||||
_onSessionError: function(error) {
|
||||
console.error(error);
|
||||
this.props.notifier.errorL10n("unable_retrieve_call_info");
|
||||
this.props.notifications.errorL10n("unable_retrieve_call_info");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -195,7 +192,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
|
||||
_setConversationTimestamp: function(err, callUrlInfo) {
|
||||
if (err) {
|
||||
this.props.notifier.errorL10n("unable_retrieve_call_info");
|
||||
this.props.notifications.errorL10n("unable_retrieve_call_info");
|
||||
} else {
|
||||
var date = (new Date(callUrlInfo.urlCreationDate * 1000));
|
||||
var options = {year: "numeric", month: "long", day: "numeric"};
|
||||
@ -343,7 +340,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
setupOutgoingCall: function() {
|
||||
var loopToken = this._conversation.get("loopToken");
|
||||
if (!loopToken) {
|
||||
this._notifier.errorL10n("missing_conversation_info");
|
||||
this._notifications.errorL10n("missing_conversation_info");
|
||||
this.navigate("home", {trigger: true});
|
||||
} else {
|
||||
var callType = this._conversation.get("selectedCallType");
|
||||
@ -361,7 +358,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
this._onSessionExpired();
|
||||
break;
|
||||
default:
|
||||
this._notifier.errorL10n("missing_conversation_info");
|
||||
this._notifications.errorL10n("missing_conversation_info");
|
||||
this.navigate("home", {trigger: true});
|
||||
break;
|
||||
}
|
||||
@ -378,7 +375,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
startCall: function() {
|
||||
var loopToken = this._conversation.get("loopToken");
|
||||
if (!loopToken) {
|
||||
this._notifier.errorL10n("missing_conversation_info");
|
||||
this._notifications.errorL10n("missing_conversation_info");
|
||||
this.navigate("home", {trigger: true});
|
||||
} else {
|
||||
this._setupWebSocketAndCallView(loopToken);
|
||||
@ -404,7 +401,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
}.bind(this), function() {
|
||||
// XXX Not the ideal response, but bug 1047410 will be replacing
|
||||
// this by better "call failed" UI.
|
||||
this._notifier.errorL10n("cannot_start_call_session_not_ready");
|
||||
this._notifications.errorL10n("cannot_start_call_session_not_ready");
|
||||
return;
|
||||
}.bind(this));
|
||||
|
||||
@ -451,7 +448,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
*/
|
||||
_handleCallRejected: function() {
|
||||
this.endCall();
|
||||
this._notifier.errorL10n("call_timeout_notification_text");
|
||||
this._notifications.errorL10n("call_timeout_notification_text");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -466,7 +463,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
},
|
||||
|
||||
_onTimeout: function() {
|
||||
this._notifier.errorL10n("call_timeout_notification_text");
|
||||
this._notifications.errorL10n("call_timeout_notification_text");
|
||||
},
|
||||
|
||||
/**
|
||||
@ -504,7 +501,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
|
||||
var startView = StartConversationView({
|
||||
model: this._conversation,
|
||||
notifier: this._notifier,
|
||||
notifications: this._notifications,
|
||||
client: this._client
|
||||
});
|
||||
this._conversation.once("call:outgoing:setup", this.setupOutgoingCall, this);
|
||||
@ -557,7 +554,7 @@ loop.webapp = (function($, _, OT, mozL10n) {
|
||||
});
|
||||
var router = new WebappRouter({
|
||||
helper: helper,
|
||||
notifier: new sharedViews.NotificationListView({el: "#messages"}),
|
||||
notifications: new sharedModels.NotificationCollection(),
|
||||
client: client,
|
||||
conversation: new sharedModels.ConversationModel({}, {
|
||||
sdk: OT,
|
||||
|
@ -11,18 +11,12 @@ describe("loop.conversation", function() {
|
||||
|
||||
var ConversationRouter = loop.conversation.ConversationRouter,
|
||||
sandbox,
|
||||
notifier;
|
||||
notifications;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
sandbox.useFakeTimers();
|
||||
notifier = {
|
||||
notify: sandbox.spy(),
|
||||
warn: sandbox.spy(),
|
||||
warnL10n: sandbox.spy(),
|
||||
error: sandbox.spy(),
|
||||
errorL10n: sandbox.spy()
|
||||
};
|
||||
notifications = new loop.shared.models.NotificationCollection();
|
||||
|
||||
navigator.mozLoop = {
|
||||
doNotDisturb: true,
|
||||
@ -73,8 +67,6 @@ describe("loop.conversation", function() {
|
||||
"initialize");
|
||||
sandbox.stub(loop.shared.models.ConversationModel.prototype,
|
||||
"initialize");
|
||||
sandbox.stub(loop.shared.views.NotificationListView.prototype,
|
||||
"initialize");
|
||||
|
||||
sandbox.stub(Backbone.history, "start");
|
||||
});
|
||||
@ -132,7 +124,7 @@ describe("loop.conversation", function() {
|
||||
router = new ConversationRouter({
|
||||
client: client,
|
||||
conversation: conversation,
|
||||
notifier: notifier
|
||||
notifications: notifications
|
||||
});
|
||||
sandbox.stub(router, "loadView");
|
||||
sandbox.stub(conversation, "incoming");
|
||||
@ -181,11 +173,12 @@ describe("loop.conversation", function() {
|
||||
|
||||
it("should display an error if requestCallsInfo returns an error",
|
||||
function(){
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
client.requestCallsInfo.callsArgWith(1, "failed");
|
||||
|
||||
router.incoming(42);
|
||||
|
||||
sinon.assert.calledOnce(notifier.errorL10n);
|
||||
sinon.assert.calledOnce(notifications.errorL10n);
|
||||
});
|
||||
|
||||
describe("requestCallsInfo successful", function() {
|
||||
@ -297,12 +290,13 @@ describe("loop.conversation", function() {
|
||||
});
|
||||
|
||||
it("should display an error", function(done) {
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
router._setupWebSocketAndCallView();
|
||||
|
||||
promise.then(function() {
|
||||
}, function () {
|
||||
sinon.assert.calledOnce(router._notifier.errorL10n);
|
||||
sinon.assert.calledWithExactly(router._notifier.errorL10n,
|
||||
sinon.assert.calledOnce(router._notifications.errorL10n);
|
||||
sinon.assert.calledWithExactly(router._notifications.errorL10n,
|
||||
"cannot_start_call_session_not_ready");
|
||||
done();
|
||||
});
|
||||
@ -374,10 +368,11 @@ describe("loop.conversation", function() {
|
||||
|
||||
it("should notify the user when session is not set",
|
||||
function() {
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
router.conversation();
|
||||
|
||||
sinon.assert.calledOnce(router._notifier.errorL10n);
|
||||
sinon.assert.calledWithExactly(router._notifier.errorL10n,
|
||||
sinon.assert.calledOnce(router._notifications.errorL10n);
|
||||
sinon.assert.calledWithExactly(router._notifications.errorL10n,
|
||||
"cannot_start_call_session_not_ready");
|
||||
});
|
||||
});
|
||||
@ -517,7 +512,7 @@ describe("loop.conversation", function() {
|
||||
router = new loop.conversation.ConversationRouter({
|
||||
client: client,
|
||||
conversation: conversation,
|
||||
notifier: notifier
|
||||
notifications: notifications
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -10,11 +10,11 @@ var TestUtils = React.addons.TestUtils;
|
||||
describe("loop.panel", function() {
|
||||
"use strict";
|
||||
|
||||
var sandbox, notifier, fakeXHR, requests = [];
|
||||
var sandbox, notifications, fakeXHR, requests = [];
|
||||
|
||||
function createTestRouter(fakeDocument) {
|
||||
return new loop.panel.PanelRouter({
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
document: fakeDocument
|
||||
});
|
||||
}
|
||||
@ -27,14 +27,7 @@ describe("loop.panel", function() {
|
||||
fakeXHR.xhr.onCreate = function (xhr) {
|
||||
requests.push(xhr);
|
||||
};
|
||||
notifier = {
|
||||
clear: sandbox.spy(),
|
||||
notify: sandbox.spy(),
|
||||
warn: sandbox.spy(),
|
||||
warnL10n: sandbox.spy(),
|
||||
error: sandbox.spy(),
|
||||
errorL10n: sandbox.spy()
|
||||
};
|
||||
notifications = new loop.shared.models.NotificationCollection();
|
||||
|
||||
navigator.mozLoop = {
|
||||
doNotDisturb: true,
|
||||
@ -63,15 +56,15 @@ describe("loop.panel", function() {
|
||||
|
||||
describe("loop.panel.PanelRouter", function() {
|
||||
describe("#constructor", function() {
|
||||
it("should require a notifier", function() {
|
||||
it("should require a notifications collection", function() {
|
||||
expect(function() {
|
||||
new loop.panel.PanelRouter();
|
||||
}).to.Throw(Error, /missing required notifier/);
|
||||
}).to.Throw(Error, /missing required notifications/);
|
||||
});
|
||||
|
||||
it("should require a document", function() {
|
||||
expect(function() {
|
||||
new loop.panel.PanelRouter({notifier: notifier});
|
||||
new loop.panel.PanelRouter({notifications: notifications});
|
||||
}).to.Throw(Error, /missing required document/);
|
||||
});
|
||||
});
|
||||
@ -101,9 +94,10 @@ describe("loop.panel", function() {
|
||||
|
||||
describe("#reset", function() {
|
||||
it("should clear all pending notifications", function() {
|
||||
sandbox.stub(notifications, "reset");
|
||||
router.reset();
|
||||
|
||||
sinon.assert.calledOnce(notifier.clear);
|
||||
sinon.assert.calledOnce(notifications.reset);
|
||||
});
|
||||
|
||||
it("should load the home view", function() {
|
||||
@ -213,7 +207,7 @@ describe("loop.panel", function() {
|
||||
};
|
||||
|
||||
view = TestUtils.renderIntoDocument(loop.panel.PanelView({
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: fakeClient
|
||||
}));
|
||||
});
|
||||
@ -324,8 +318,9 @@ describe("loop.panel", function() {
|
||||
}
|
||||
};
|
||||
|
||||
sandbox.stub(notifications, "reset");
|
||||
view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: fakeClient
|
||||
}));
|
||||
});
|
||||
@ -350,7 +345,7 @@ describe("loop.panel", function() {
|
||||
it("should make a request to requestCallUrl", function() {
|
||||
sandbox.stub(fakeClient, "requestCallUrl");
|
||||
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: fakeClient
|
||||
}));
|
||||
|
||||
@ -363,7 +358,7 @@ describe("loop.panel", function() {
|
||||
// Cancel requestCallUrl effect to keep the state pending
|
||||
fakeClient.requestCallUrl = sandbox.stub();
|
||||
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: fakeClient
|
||||
}));
|
||||
|
||||
@ -387,14 +382,14 @@ describe("loop.panel", function() {
|
||||
});
|
||||
|
||||
it("should reset all pending notifications", function() {
|
||||
sinon.assert.calledOnce(view.props.notifier.clear);
|
||||
sinon.assert.calledOnce(view.props.notifications.reset);
|
||||
});
|
||||
|
||||
it("should display a share button for email", function() {
|
||||
fakeClient.requestCallUrl = sandbox.stub();
|
||||
var mailto = 'mailto:?subject=email-subject&body=http://example.com';
|
||||
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: fakeClient
|
||||
}));
|
||||
view.setState({pending: false, callUrl: "http://example.com"});
|
||||
@ -407,7 +402,7 @@ describe("loop.panel", function() {
|
||||
it("should feature a copy button capable of copying the call url when clicked", function() {
|
||||
fakeClient.requestCallUrl = sandbox.stub();
|
||||
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: fakeClient
|
||||
}));
|
||||
view.setState({
|
||||
@ -427,7 +422,7 @@ describe("loop.panel", function() {
|
||||
it("should note the call url expiry when the url is copied via button",
|
||||
function() {
|
||||
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: fakeClient
|
||||
}));
|
||||
view.setState({
|
||||
@ -447,7 +442,7 @@ describe("loop.panel", function() {
|
||||
it("should note the call url expiry when the url is emailed",
|
||||
function() {
|
||||
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: fakeClient
|
||||
}));
|
||||
view.setState({
|
||||
@ -468,7 +463,7 @@ describe("loop.panel", function() {
|
||||
it("should note the call url expiry when the url is copied manually",
|
||||
function() {
|
||||
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: fakeClient
|
||||
}));
|
||||
view.setState({
|
||||
@ -490,13 +485,14 @@ describe("loop.panel", function() {
|
||||
fakeClient.requestCallUrl = function(_, cb) {
|
||||
cb("fake error");
|
||||
};
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: fakeClient
|
||||
}));
|
||||
|
||||
sinon.assert.calledOnce(notifier.errorL10n);
|
||||
sinon.assert.calledWithExactly(notifier.errorL10n,
|
||||
sinon.assert.calledOnce(notifications.errorL10n);
|
||||
sinon.assert.calledWithExactly(notifications.errorL10n,
|
||||
"unable_retrieve_url");
|
||||
});
|
||||
});
|
||||
|
@ -401,4 +401,58 @@ describe("loop.shared.models", function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("NotificationCollection", function() {
|
||||
var collection, notifData, testNotif;
|
||||
|
||||
beforeEach(function() {
|
||||
collection = new sharedModels.NotificationCollection();
|
||||
sandbox.stub(l10n, "get", function(x) {
|
||||
return "translated:" + x;
|
||||
});
|
||||
notifData = {level: "error", message: "plop"};
|
||||
testNotif = new sharedModels.NotificationModel(notifData);
|
||||
});
|
||||
|
||||
describe("#warn", function() {
|
||||
it("should add a warning notification to the stack", function() {
|
||||
collection.warn("watch out");
|
||||
|
||||
expect(collection).to.have.length.of(1);
|
||||
expect(collection.at(0).get("level")).eql("warning");
|
||||
expect(collection.at(0).get("message")).eql("watch out");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#warnL10n", function() {
|
||||
it("should warn using a l10n string id", function() {
|
||||
collection.warnL10n("fakeId");
|
||||
|
||||
expect(collection).to.have.length.of(1);
|
||||
expect(collection.at(0).get("level")).eql("warning");
|
||||
expect(collection.at(0).get("message")).eql("translated:fakeId");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#error", function() {
|
||||
it("should add an error notification to the stack", function() {
|
||||
collection.error("wrong");
|
||||
|
||||
expect(collection).to.have.length.of(1);
|
||||
expect(collection.at(0).get("level")).eql("error");
|
||||
expect(collection.at(0).get("message")).eql("wrong");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#errorL10n", function() {
|
||||
it("should notify an error using a l10n string id", function() {
|
||||
collection.errorL10n("fakeId");
|
||||
|
||||
expect(collection).to.have.length.of(1);
|
||||
expect(collection.at(0).get("level")).eql("error");
|
||||
expect(collection.at(0).get("message")).eql("translated:fakeId");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -9,17 +9,13 @@ var expect = chai.expect;
|
||||
describe("loop.shared.router", function() {
|
||||
"use strict";
|
||||
|
||||
var sandbox, notifier;
|
||||
var sandbox, notifications;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
notifier = {
|
||||
notify: sandbox.spy(),
|
||||
warn: sandbox.spy(),
|
||||
warnL10n: sandbox.spy(),
|
||||
error: sandbox.spy(),
|
||||
errorL10n: sandbox.spy()
|
||||
};
|
||||
notifications = new loop.shared.models.NotificationCollection();
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
sandbox.stub(notifications, "warnL10n");
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
@ -36,19 +32,19 @@ describe("loop.shared.router", function() {
|
||||
});
|
||||
|
||||
describe("#constructor", function() {
|
||||
it("should require a notifier", function() {
|
||||
it("should require a notifications collection", function() {
|
||||
expect(function() {
|
||||
new loop.shared.router.BaseRouter();
|
||||
}).to.Throw(Error, /missing required notifier/);
|
||||
}).to.Throw(Error, /missing required notifications/);
|
||||
});
|
||||
|
||||
describe("inherited", function() {
|
||||
var ExtendedRouter = loop.shared.router.BaseRouter.extend({});
|
||||
|
||||
it("should require a notifier", function() {
|
||||
it("should require a notifications collection", function() {
|
||||
expect(function() {
|
||||
new ExtendedRouter();
|
||||
}).to.Throw(Error, /missing required notifier/);
|
||||
}).to.Throw(Error, /missing required notifications/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -62,7 +58,7 @@ describe("loop.shared.router", function() {
|
||||
template: _.template("<p>plop</p>")
|
||||
});
|
||||
view = new TestView();
|
||||
router = new TestRouter({notifier: notifier});
|
||||
router = new TestRouter({notifications: notifications});
|
||||
});
|
||||
|
||||
describe("#loadView", function() {
|
||||
@ -131,7 +127,7 @@ describe("loop.shared.router", function() {
|
||||
};
|
||||
router = new TestRouter({
|
||||
conversation: conversation,
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: {}
|
||||
});
|
||||
});
|
||||
@ -141,8 +137,8 @@ describe("loop.shared.router", function() {
|
||||
it("should warn the user when .connect() call fails", function() {
|
||||
conversation.trigger("session:connection-error");
|
||||
|
||||
sinon.assert.calledOnce(notifier.errorL10n);
|
||||
sinon.assert.calledWithExactly(notifier.errorL10n, sinon.match.string);
|
||||
sinon.assert.calledOnce(notifications.errorL10n);
|
||||
sinon.assert.calledWithExactly(notifications.errorL10n, sinon.match.string);
|
||||
});
|
||||
|
||||
it("should invoke endCall()", function() {
|
||||
@ -163,8 +159,8 @@ describe("loop.shared.router", function() {
|
||||
it("should warn the user when peer hangs up", function() {
|
||||
conversation.trigger("session:peer-hungup");
|
||||
|
||||
sinon.assert.calledOnce(notifier.warnL10n);
|
||||
sinon.assert.calledWithExactly(notifier.warnL10n,
|
||||
sinon.assert.calledOnce(notifications.warnL10n);
|
||||
sinon.assert.calledWithExactly(notifications.warnL10n,
|
||||
"peer_ended_conversation2");
|
||||
|
||||
});
|
||||
@ -178,8 +174,8 @@ describe("loop.shared.router", function() {
|
||||
it("should warn the user when network disconnects", function() {
|
||||
conversation.trigger("session:network-disconnected");
|
||||
|
||||
sinon.assert.calledOnce(notifier.warnL10n);
|
||||
sinon.assert.calledWithExactly(notifier.warnL10n,
|
||||
sinon.assert.calledOnce(notifications.warnL10n);
|
||||
sinon.assert.calledWithExactly(notifications.warnL10n,
|
||||
"network_disconnected");
|
||||
});
|
||||
|
||||
|
@ -597,195 +597,28 @@ describe("loop.shared.views", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("NotificationView", function() {
|
||||
var collection, model, view;
|
||||
|
||||
beforeEach(function() {
|
||||
$("#fixtures").append('<div id="test-notif"></div>');
|
||||
model = new sharedModels.NotificationModel({
|
||||
level: "error",
|
||||
message: "plop"
|
||||
});
|
||||
collection = new sharedModels.NotificationCollection([model]);
|
||||
view = new sharedViews.NotificationView({
|
||||
el: $("#test-notif"),
|
||||
collection: collection,
|
||||
model: model
|
||||
});
|
||||
});
|
||||
|
||||
describe("#dismiss", function() {
|
||||
it("should automatically dismiss notification after 500ms", function() {
|
||||
view.render().dismiss({preventDefault: sandbox.spy()});
|
||||
|
||||
expect(view.$(".message").text()).eql("plop");
|
||||
|
||||
sandbox.clock.tick(500);
|
||||
|
||||
expect(collection).to.have.length.of(0);
|
||||
expect($("#test-notif").html()).eql(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#render", function() {
|
||||
it("should render template with model attribute values", function() {
|
||||
view.render();
|
||||
|
||||
expect(view.$(".message").text()).eql("plop");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("NotificationListView", function() {
|
||||
var coll, notifData, testNotif;
|
||||
var coll, view, testNotif;
|
||||
|
||||
function mountTestComponent(props) {
|
||||
return TestUtils.renderIntoDocument(sharedViews.NotificationListView(props));
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox.stub(l10n, "get", function(x) {
|
||||
return "translated:" + x;
|
||||
});
|
||||
notifData = {level: "error", message: "plop"};
|
||||
testNotif = new sharedModels.NotificationModel(notifData);
|
||||
coll = new sharedModels.NotificationCollection();
|
||||
view = mountTestComponent({notifications: coll});
|
||||
testNotif = {level: "warning", message: "foo"};
|
||||
sinon.spy(view, "render");
|
||||
});
|
||||
|
||||
describe("#initialize", function() {
|
||||
it("should accept a collection option", function() {
|
||||
var view = new sharedViews.NotificationListView({collection: coll});
|
||||
|
||||
expect(view.collection).to.be.an.instanceOf(
|
||||
sharedModels.NotificationCollection);
|
||||
});
|
||||
|
||||
it("should set a default collection when none is passed", function() {
|
||||
var view = new sharedViews.NotificationListView();
|
||||
|
||||
expect(view.collection).to.be.an.instanceOf(
|
||||
sharedModels.NotificationCollection);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#clear", function() {
|
||||
it("should clear all notifications from the collection", function() {
|
||||
var view = new sharedViews.NotificationListView();
|
||||
view.notify(testNotif);
|
||||
|
||||
view.clear();
|
||||
|
||||
expect(coll).to.have.length.of(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("#notify", function() {
|
||||
var view;
|
||||
|
||||
beforeEach(function() {
|
||||
view = new sharedViews.NotificationListView({collection: coll});
|
||||
});
|
||||
|
||||
describe("adds a new notification to the stack", function() {
|
||||
it("using a plain object", function() {
|
||||
view.notify(notifData);
|
||||
|
||||
expect(coll).to.have.length.of(1);
|
||||
});
|
||||
|
||||
it("using a NotificationModel instance", function() {
|
||||
view.notify(testNotif);
|
||||
|
||||
expect(coll).to.have.length.of(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#notifyL10n", function() {
|
||||
var view;
|
||||
|
||||
beforeEach(function() {
|
||||
view = new sharedViews.NotificationListView({collection: coll});
|
||||
});
|
||||
|
||||
it("should translate a message string identifier", function() {
|
||||
view.notifyL10n("fakeId", "warning");
|
||||
|
||||
sinon.assert.calledOnce(l10n.get);
|
||||
sinon.assert.calledWithExactly(l10n.get, "fakeId");
|
||||
});
|
||||
|
||||
it("should notify end user with the provided message", function() {
|
||||
sandbox.stub(view, "notify");
|
||||
|
||||
view.notifyL10n("fakeId", "warning");
|
||||
|
||||
sinon.assert.calledOnce(view.notify);
|
||||
sinon.assert.calledWithExactly(view.notify, {
|
||||
message: "translated:fakeId",
|
||||
level: "warning"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#warn", function() {
|
||||
it("should add a warning notification to the stack", function() {
|
||||
var view = new sharedViews.NotificationListView({collection: coll});
|
||||
|
||||
view.warn("watch out");
|
||||
|
||||
expect(coll).to.have.length.of(1);
|
||||
expect(coll.at(0).get("level")).eql("warning");
|
||||
expect(coll.at(0).get("message")).eql("watch out");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#warnL10n", function() {
|
||||
it("should warn using a l10n string id", function() {
|
||||
var view = new sharedViews.NotificationListView({collection: coll});
|
||||
sandbox.stub(view, "notify");
|
||||
|
||||
view.warnL10n("fakeId");
|
||||
|
||||
sinon.assert.called(view.notify);
|
||||
sinon.assert.calledWithExactly(view.notify, {
|
||||
message: "translated:fakeId",
|
||||
level: "warning"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("#error", function() {
|
||||
it("should add an error notification to the stack", function() {
|
||||
var view = new sharedViews.NotificationListView({collection: coll});
|
||||
|
||||
view.error("wrong");
|
||||
|
||||
expect(coll).to.have.length.of(1);
|
||||
expect(coll.at(0).get("level")).eql("error");
|
||||
expect(coll.at(0).get("message")).eql("wrong");
|
||||
});
|
||||
});
|
||||
|
||||
describe("#errorL10n", function() {
|
||||
it("should notify an error using a l10n string id", function() {
|
||||
var view = new sharedViews.NotificationListView({collection: coll});
|
||||
sandbox.stub(view, "notify");
|
||||
|
||||
view.errorL10n("fakeId");
|
||||
|
||||
sinon.assert.called(view.notify);
|
||||
sinon.assert.calledWithExactly(view.notify, {
|
||||
message: "translated:fakeId",
|
||||
level: "error"
|
||||
});
|
||||
});
|
||||
afterEach(function() {
|
||||
view.render.restore();
|
||||
});
|
||||
|
||||
describe("Collection events", function() {
|
||||
var view;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox.stub(sharedViews.NotificationListView.prototype, "render");
|
||||
view = new sharedViews.NotificationListView({collection: coll});
|
||||
});
|
||||
|
||||
it("should render when a notification is added to the collection",
|
||||
function() {
|
||||
coll.add(testNotif);
|
||||
@ -798,7 +631,7 @@ describe("loop.shared.views", function() {
|
||||
coll.add(testNotif);
|
||||
coll.remove(testNotif);
|
||||
|
||||
sinon.assert.calledTwice(view.render);
|
||||
sinon.assert.calledOnce(view.render);
|
||||
});
|
||||
|
||||
it("should render when the collection is reset", function() {
|
||||
|
@ -13,20 +13,14 @@ describe("loop.webapp", function() {
|
||||
var sharedModels = loop.shared.models,
|
||||
sharedViews = loop.shared.views,
|
||||
sandbox,
|
||||
notifier;
|
||||
notifications;
|
||||
|
||||
beforeEach(function() {
|
||||
sandbox = sinon.sandbox.create();
|
||||
// conversation#outgoing sets timers, so we need to use fake ones
|
||||
// to prevent random failures.
|
||||
sandbox.useFakeTimers();
|
||||
notifier = {
|
||||
notify: sandbox.spy(),
|
||||
warn: sandbox.spy(),
|
||||
warnL10n: sandbox.spy(),
|
||||
error: sandbox.spy(),
|
||||
errorL10n: sandbox.spy(),
|
||||
};
|
||||
notifications = new sharedModels.NotificationCollection();
|
||||
loop.config.pendingCallTimeout = 1000;
|
||||
});
|
||||
|
||||
@ -88,7 +82,7 @@ describe("loop.webapp", function() {
|
||||
helper: {},
|
||||
client: client,
|
||||
conversation: conversation,
|
||||
notifier: notifier
|
||||
notifications: notifications
|
||||
});
|
||||
sandbox.stub(router, "loadView");
|
||||
sandbox.stub(router, "navigate");
|
||||
@ -107,10 +101,11 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
|
||||
it("should notify the user if session token is missing", function() {
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
router.startCall();
|
||||
|
||||
sinon.assert.calledOnce(notifier.errorL10n);
|
||||
sinon.assert.calledWithExactly(notifier.errorL10n,
|
||||
sinon.assert.calledOnce(notifications.errorL10n);
|
||||
sinon.assert.calledWithExactly(notifications.errorL10n,
|
||||
"missing_conversation_info");
|
||||
});
|
||||
|
||||
@ -194,13 +189,14 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
});
|
||||
|
||||
it("should display an error", function() {
|
||||
it("should display an error", function(done) {
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
router._setupWebSocketAndCallView();
|
||||
|
||||
promise.then(function() {
|
||||
}, function () {
|
||||
sinon.assert.calledOnce(router._notifier.errorL10n);
|
||||
sinon.assert.calledWithExactly(router._notifier.errorL10n,
|
||||
sinon.assert.calledOnce(router._notifications.errorL10n);
|
||||
sinon.assert.calledWithExactly(router._notifications.errorL10n,
|
||||
"cannot_start_call_session_not_ready");
|
||||
done();
|
||||
});
|
||||
@ -242,13 +238,15 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
|
||||
it("should display an error message", function() {
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
|
||||
router._websocket.trigger("progress", {
|
||||
state: "terminated",
|
||||
reason: "reject"
|
||||
});
|
||||
|
||||
sinon.assert.calledOnce(router._notifier.errorL10n);
|
||||
sinon.assert.calledWithExactly(router._notifier.errorL10n,
|
||||
sinon.assert.calledOnce(router._notifications.errorL10n);
|
||||
sinon.assert.calledWithExactly(router._notifications.errorL10n,
|
||||
"call_timeout_notification_text");
|
||||
});
|
||||
});
|
||||
@ -472,9 +470,10 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
|
||||
it("should display an error", function() {
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
conversation.setupOutgoingCall();
|
||||
|
||||
sinon.assert.calledOnce(notifier.errorL10n);
|
||||
sinon.assert.calledOnce(notifications.errorL10n);
|
||||
});
|
||||
});
|
||||
|
||||
@ -513,10 +512,11 @@ describe("loop.webapp", function() {
|
||||
});
|
||||
|
||||
it("should notify the user on any other error", function() {
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
client.requestCallInfo.callsArgWith(2, {errno: 104});
|
||||
conversation.setupOutgoingCall();
|
||||
|
||||
sinon.assert.calledOnce(notifier.errorL10n);
|
||||
sinon.assert.calledOnce(notifications.errorL10n);
|
||||
});
|
||||
|
||||
it("should call outgoing on the conversation model when details " +
|
||||
@ -565,7 +565,7 @@ describe("loop.webapp", function() {
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.StartConversationView({
|
||||
model: conversation,
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: standaloneClientStub
|
||||
})
|
||||
);
|
||||
@ -657,7 +657,7 @@ describe("loop.webapp", function() {
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.StartConversationView({
|
||||
model: conversation,
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: {requestCallUrlInfo: requestCallUrlInfo}
|
||||
})
|
||||
);
|
||||
@ -678,10 +678,11 @@ describe("loop.webapp", function() {
|
||||
|
||||
it("should trigger a notication when a session:error model event is " +
|
||||
" received", function() {
|
||||
sandbox.stub(notifications, "errorL10n");
|
||||
conversation.trigger("session:error", "tech error");
|
||||
|
||||
sinon.assert.calledOnce(notifier.errorL10n);
|
||||
sinon.assert.calledWithExactly(notifier.errorL10n,
|
||||
sinon.assert.calledOnce(notifications.errorL10n);
|
||||
sinon.assert.calledWithExactly(notifications.errorL10n,
|
||||
"unable_retrieve_call_info");
|
||||
});
|
||||
});
|
||||
@ -714,7 +715,7 @@ describe("loop.webapp", function() {
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.StartConversationView({
|
||||
model: conversation,
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: {requestCallUrlInfo: requestCallUrlInfo}
|
||||
})
|
||||
);
|
||||
@ -730,7 +731,7 @@ describe("loop.webapp", function() {
|
||||
view = React.addons.TestUtils.renderIntoDocument(
|
||||
loop.webapp.StartConversationView({
|
||||
model: conversation,
|
||||
notifier: notifier,
|
||||
notifications: notifications,
|
||||
client: {requestCallUrlInfo: requestCallUrlInfo}
|
||||
})
|
||||
);
|
||||
|
@ -58,7 +58,9 @@
|
||||
});
|
||||
mockConversationModel.startSession = noop;
|
||||
|
||||
var mockNotifier = {};
|
||||
var notifications = new loop.shared.models.NotificationCollection();
|
||||
var errNotifications = new loop.shared.models.NotificationCollection();
|
||||
errNotifications.error("Error!");
|
||||
|
||||
var Example = React.createClass({displayName: 'Example',
|
||||
render: function() {
|
||||
@ -117,11 +119,14 @@
|
||||
React.DOM.strong(null, "Note:"), " 332px wide."
|
||||
),
|
||||
Example({summary: "Call URL retrieved", dashed: "true", style: {width: "332px"}},
|
||||
PanelView({client: mockClient, notifier: mockNotifier,
|
||||
PanelView({client: mockClient, notifications: notifications,
|
||||
callUrl: "http://invalid.example.url/"})
|
||||
),
|
||||
Example({summary: "Pending call url retrieval", dashed: "true", style: {width: "332px"}},
|
||||
PanelView({client: mockClient, notifier: mockNotifier})
|
||||
PanelView({client: mockClient, notifications: notifications})
|
||||
),
|
||||
Example({summary: "Error Notification", dashed: "true", style: {width: "332px"}},
|
||||
PanelView({client: mockClient, notifications: errNotifications})
|
||||
)
|
||||
),
|
||||
|
||||
@ -192,7 +197,7 @@
|
||||
React.DOM.div({className: "standalone"},
|
||||
StartConversationView({model: mockConversationModel,
|
||||
client: mockClient,
|
||||
notifier: mockNotifier,
|
||||
notifications: notifications,
|
||||
showCallOptionsMenu: true})
|
||||
)
|
||||
)
|
||||
|
@ -58,7 +58,9 @@
|
||||
});
|
||||
mockConversationModel.startSession = noop;
|
||||
|
||||
var mockNotifier = {};
|
||||
var notifications = new loop.shared.models.NotificationCollection();
|
||||
var errNotifications = new loop.shared.models.NotificationCollection();
|
||||
errNotifications.error("Error!");
|
||||
|
||||
var Example = React.createClass({
|
||||
render: function() {
|
||||
@ -117,11 +119,14 @@
|
||||
<strong>Note:</strong> 332px wide.
|
||||
</p>
|
||||
<Example summary="Call URL retrieved" dashed="true" style={{width: "332px"}}>
|
||||
<PanelView client={mockClient} notifier={mockNotifier}
|
||||
<PanelView client={mockClient} notifications={notifications}
|
||||
callUrl="http://invalid.example.url/" />
|
||||
</Example>
|
||||
<Example summary="Pending call url retrieval" dashed="true" style={{width: "332px"}}>
|
||||
<PanelView client={mockClient} notifier={mockNotifier} />
|
||||
<PanelView client={mockClient} notifications={notifications} />
|
||||
</Example>
|
||||
<Example summary="Error Notification" dashed="true" style={{width: "332px"}}>
|
||||
<PanelView client={mockClient} notifications={errNotifications}/>
|
||||
</Example>
|
||||
</Section>
|
||||
|
||||
@ -192,7 +197,7 @@
|
||||
<div className="standalone">
|
||||
<StartConversationView model={mockConversationModel}
|
||||
client={mockClient}
|
||||
notifier={mockNotifier}
|
||||
notifications={notifications}
|
||||
showCallOptionsMenu={true} />
|
||||
</div>
|
||||
</Example>
|
||||
|
@ -77,20 +77,20 @@
|
||||
<h1 class="titleText showPrivate">&aboutPrivateBrowsing.title;</h1>
|
||||
<h1 class="titleText showNormal">&aboutPrivateBrowsing.title.normal;</h1>
|
||||
|
||||
<p class="showPrivate">&aboutPrivateBrowsing.subtitle;</p>
|
||||
<p class="showNormal">&aboutPrivateBrowsing.subtitle.normal;</p>
|
||||
<p class="subtitleText showPrivate">&aboutPrivateBrowsing.subtitle;</p>
|
||||
<p class="subtitleText showNormal">&aboutPrivateBrowsing.subtitle.normal;</p>
|
||||
|
||||
<p>&aboutPrivateBrowsing.description;</p>
|
||||
<p class="descriptionText">&aboutPrivateBrowsing.description;</p>
|
||||
|
||||
<p class="showNormal">&aboutPrivateBrowsing.notPrivate;</p>
|
||||
<p class="notPrivateText showNormal">&aboutPrivateBrowsing.notPrivate;</p>
|
||||
|
||||
<button xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
class="showNormal"
|
||||
class="openPrivate showNormal"
|
||||
label="&privatebrowsingpage.openPrivateWindow.label;"
|
||||
accesskey="&privatebrowsingpage.openPrivateWindow.accesskey;"
|
||||
oncommand="openPrivateWindow();"/>
|
||||
<div class="showPrivate">
|
||||
<p>&aboutPrivateBrowsing.moreInfo;</p>
|
||||
<p class="moreInfoText">&aboutPrivateBrowsing.moreInfo;</p>
|
||||
<p><a id="learnMore" target="_blank">&aboutPrivateBrowsing.learnMore;</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,19 +4,21 @@
|
||||
// Tests devtools API
|
||||
|
||||
const Cu = Components.utils;
|
||||
const toolId = "test-tool";
|
||||
const toolId1 = "test-tool-1";
|
||||
const toolId2 = "test-tool-2";
|
||||
|
||||
let tempScope = {};
|
||||
Cu.import("resource://gre/modules/devtools/event-emitter.js", tempScope);
|
||||
let EventEmitter = tempScope.EventEmitter;
|
||||
|
||||
function test() {
|
||||
addTab("about:blank").then(runTests);
|
||||
addTab("about:blank").then(runTests1);
|
||||
}
|
||||
|
||||
function runTests(aTab) {
|
||||
// Test scenario 1: the tool definition build method returns a promise.
|
||||
function runTests1(aTab) {
|
||||
let toolDefinition = {
|
||||
id: toolId,
|
||||
id: toolId1,
|
||||
isTargetSupported: function() true,
|
||||
visibilityswitch: "devtools.test-tool.enabled",
|
||||
url: "about:blank",
|
||||
@ -28,40 +30,131 @@ function runTests(aTab) {
|
||||
};
|
||||
|
||||
ok(gDevTools, "gDevTools exists");
|
||||
is(gDevTools.getToolDefinitionMap().has(toolId), false,
|
||||
is(gDevTools.getToolDefinitionMap().has(toolId1), false,
|
||||
"The tool is not registered");
|
||||
|
||||
gDevTools.registerTool(toolDefinition);
|
||||
is(gDevTools.getToolDefinitionMap().has(toolId), true,
|
||||
is(gDevTools.getToolDefinitionMap().has(toolId1), true,
|
||||
"The tool is registered");
|
||||
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
gDevTools.showToolbox(target, toolId).then(function (toolbox) {
|
||||
let events = {};
|
||||
|
||||
// Check events on the gDevTools and toolbox objects.
|
||||
gDevTools.once(toolId1 + "-init", (event, toolbox, iframe) => {
|
||||
ok(iframe, "iframe argument available");
|
||||
|
||||
toolbox.once(toolId1 + "-init", (event, iframe) => {
|
||||
ok(iframe, "iframe argument available");
|
||||
events["init"] = true;
|
||||
});
|
||||
});
|
||||
|
||||
gDevTools.once(toolId1 + "-ready", (event, toolbox, panel) => {
|
||||
ok(panel, "panel argument available");
|
||||
|
||||
toolbox.once(toolId1 + "-ready", (event, panel) => {
|
||||
ok(panel, "panel argument available");
|
||||
events["ready"] = true;
|
||||
});
|
||||
});
|
||||
|
||||
gDevTools.showToolbox(target, toolId1).then(function (toolbox) {
|
||||
is(toolbox.target, target, "toolbox target is correct");
|
||||
is(toolbox._host.hostTab, gBrowser.selectedTab, "toolbox host is correct");
|
||||
|
||||
ok(events["init"], "init event fired");
|
||||
ok(events["ready"], "ready event fired");
|
||||
|
||||
gDevTools.unregisterTool(toolId1);
|
||||
|
||||
runTests2();
|
||||
});
|
||||
}
|
||||
|
||||
// Test scenario 2: the tool definition build method returns panel instance.
|
||||
function runTests2() {
|
||||
let toolDefinition = {
|
||||
id: toolId2,
|
||||
isTargetSupported: function() true,
|
||||
visibilityswitch: "devtools.test-tool.enabled",
|
||||
url: "about:blank",
|
||||
label: "someLabel",
|
||||
build: function(iframeWindow, toolbox) {
|
||||
return new DevToolPanel(iframeWindow, toolbox);
|
||||
},
|
||||
};
|
||||
|
||||
is(gDevTools.getToolDefinitionMap().has(toolId2), false,
|
||||
"The tool is not registered");
|
||||
|
||||
gDevTools.registerTool(toolDefinition);
|
||||
is(gDevTools.getToolDefinitionMap().has(toolId2), true,
|
||||
"The tool is registered");
|
||||
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
let events = {};
|
||||
|
||||
// Check events on the gDevTools and toolbox objects.
|
||||
gDevTools.once(toolId2 + "-init", (event, toolbox, iframe) => {
|
||||
ok(iframe, "iframe argument available");
|
||||
|
||||
toolbox.once(toolId2 + "-init", (event, iframe) => {
|
||||
ok(iframe, "iframe argument available");
|
||||
events["init"] = true;
|
||||
});
|
||||
});
|
||||
|
||||
gDevTools.once(toolId2 + "-build", (event, toolbox, panel, iframe) => {
|
||||
ok(panel, "panel argument available");
|
||||
|
||||
toolbox.once(toolId2 + "-build", (event, panel, iframe) => {
|
||||
ok(panel, "panel argument available");
|
||||
events["build"] = true;
|
||||
});
|
||||
});
|
||||
|
||||
gDevTools.once(toolId2 + "-ready", (event, toolbox, panel) => {
|
||||
ok(panel, "panel argument available");
|
||||
|
||||
toolbox.once(toolId2 + "-ready", (event, panel) => {
|
||||
ok(panel, "panel argument available");
|
||||
events["ready"] = true;
|
||||
});
|
||||
});
|
||||
|
||||
gDevTools.showToolbox(target, toolId2).then(function (toolbox) {
|
||||
is(toolbox.target, target, "toolbox target is correct");
|
||||
is(toolbox._host.hostTab, gBrowser.selectedTab, "toolbox host is correct");
|
||||
|
||||
ok(events["init"], "init event fired");
|
||||
ok(events["build"], "build event fired");
|
||||
ok(events["ready"], "ready event fired");
|
||||
|
||||
continueTests(toolbox);
|
||||
});
|
||||
}
|
||||
|
||||
function continueTests(toolbox, panel) {
|
||||
ok(toolbox.getCurrentPanel(), "panel value is correct");
|
||||
is(toolbox.currentToolId, toolId, "toolbox _currentToolId is correct");
|
||||
is(toolbox.currentToolId, toolId2, "toolbox _currentToolId is correct");
|
||||
|
||||
ok(!toolbox.doc.getElementById("toolbox-tab-" + toolId).hasAttribute("icon-invertable"),
|
||||
ok(!toolbox.doc.getElementById("toolbox-tab-" + toolId2).hasAttribute("icon-invertable"),
|
||||
"The tool tab does not have the invertable attribute");
|
||||
|
||||
ok(toolbox.doc.getElementById("toolbox-tab-inspector").hasAttribute("icon-invertable"),
|
||||
"The builtin tool tabs do have the invertable attribute");
|
||||
|
||||
let toolDefinitions = gDevTools.getToolDefinitionMap();
|
||||
is(toolDefinitions.has(toolId), true, "The tool is in gDevTools");
|
||||
is(toolDefinitions.has(toolId2), true, "The tool is in gDevTools");
|
||||
|
||||
let toolDefinition = toolDefinitions.get(toolId);
|
||||
is(toolDefinition.id, toolId, "toolDefinition id is correct");
|
||||
let toolDefinition = toolDefinitions.get(toolId2);
|
||||
is(toolDefinition.id, toolId2, "toolDefinition id is correct");
|
||||
|
||||
gDevTools.unregisterTool(toolId);
|
||||
is(gDevTools.getToolDefinitionMap().has(toolId), false,
|
||||
gDevTools.unregisterTool(toolId2);
|
||||
is(gDevTools.getToolDefinitionMap().has(toolId2), false,
|
||||
"The tool is no longer registered");
|
||||
|
||||
// Wait for unregisterTool to select the next tool before
|
||||
|
@ -1,46 +1,60 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that changing preferences in the options panel updates the prefs
|
||||
// and toggles appropriate things in the toolbox.
|
||||
|
||||
let doc = null, toolbox = null, panelWin = null, modifiedPrefs = [];
|
||||
|
||||
function test() {
|
||||
let test = asyncTest(function*() {
|
||||
const URL = "data:text/html;charset=utf8,test for dynamically registering and unregistering tools";
|
||||
Task.spawn(function* () {
|
||||
let tab = yield addTab(URL);
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let toolbox = yield gDevTools.showToolbox(target);
|
||||
yield testSelectTool(toolbox);
|
||||
yield testOptionsShortcut();
|
||||
yield testOptions();
|
||||
yield testToggleTools();
|
||||
}).then(cleanup, errorHandler);
|
||||
}
|
||||
|
||||
function testSelectTool(aToolbox) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
toolbox = aToolbox;
|
||||
registerNewTool();
|
||||
let tab = yield addTab(URL);
|
||||
let target = TargetFactory.forTab(tab);
|
||||
toolbox = yield gDevTools.showToolbox(target);
|
||||
doc = toolbox.doc;
|
||||
toolbox.once("options-selected", () => {
|
||||
ok(true, "Toolbox selected via selectTool method");
|
||||
deferred.resolve();
|
||||
});
|
||||
toolbox.selectTool("options");
|
||||
yield testSelectTool();
|
||||
yield testOptionsShortcut();
|
||||
yield testOptions();
|
||||
yield testToggleTools();
|
||||
yield cleanup();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
function registerNewTool() {
|
||||
let toolDefinition = {
|
||||
id: "test-tool",
|
||||
isTargetSupported: function() true,
|
||||
visibilityswitch: "devtools.test-tool.enabled",
|
||||
url: "about:blank",
|
||||
label: "someLabel"
|
||||
};
|
||||
|
||||
ok(gDevTools, "gDevTools exists");
|
||||
ok(!gDevTools.getToolDefinitionMap().has("test-tool"),
|
||||
"The tool is not registered");
|
||||
|
||||
gDevTools.registerTool(toolDefinition);
|
||||
ok(gDevTools.getToolDefinitionMap().has("test-tool"),
|
||||
"The tool is registered");
|
||||
}
|
||||
|
||||
function testOptionsShortcut() {
|
||||
let deferred = promise.defer();
|
||||
function* testSelectTool() {
|
||||
info ("Checking to make sure that the options panel can be selected.");
|
||||
|
||||
toolbox.selectTool("webconsole")
|
||||
let onceSelected = toolbox.once("options-selected");
|
||||
toolbox.selectTool("options");
|
||||
yield onceSelected;
|
||||
ok(true, "Toolbox selected via selectTool method");
|
||||
}
|
||||
|
||||
function* testOptionsShortcut() {
|
||||
info ("Selecting another tool, then reselecting options panel with keyboard.");
|
||||
|
||||
yield toolbox.selectTool("webconsole")
|
||||
.then(() => synthesizeKeyFromKeyTag("toolbox-options-key", doc))
|
||||
.then(() => {
|
||||
ok(true, "Toolbox selected via shortcut key");
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function* testOptions() {
|
||||
@ -103,7 +117,7 @@ function* testMenuList(menulist) {
|
||||
}
|
||||
}
|
||||
|
||||
function testMouseClick(node, prefValue) {
|
||||
function* testMouseClick(node, prefValue) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let pref = node.getAttribute("data-pref");
|
||||
@ -127,14 +141,17 @@ function testMouseClick(node, prefValue) {
|
||||
EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
yield deferred.promise;
|
||||
}
|
||||
|
||||
function testToggleTools() {
|
||||
let toolNodes = panelWin.document.querySelectorAll("#default-tools-box > checkbox:not([unsupported])");
|
||||
let enabledTools = Array.prototype.filter.call(toolNodes, node => node.checked);
|
||||
function* testToggleTools() {
|
||||
let toolNodes = panelWin.document.querySelectorAll("#default-tools-box > checkbox:not([unsupported]), #additional-tools-box > checkbox:not([unsupported])");
|
||||
let enabledTools = [...toolNodes].filter(node => node.checked);
|
||||
|
||||
let toggleableTools = gDevTools.getDefaultTools().filter(tool => {
|
||||
return tool.visibilityswitch;
|
||||
}).concat(gDevTools.getAdditionalTools());
|
||||
|
||||
let toggleableTools = gDevTools.getDefaultTools().filter(tool => tool.visibilityswitch);
|
||||
for (let node of toolNodes) {
|
||||
let id = node.getAttribute("id");
|
||||
ok (toggleableTools.some(tool => tool.id === id),
|
||||
@ -148,23 +165,22 @@ function testToggleTools() {
|
||||
}
|
||||
|
||||
// Toggle each tool
|
||||
let p = promise.resolve();
|
||||
for (let node of toolNodes) {
|
||||
p = p.then(toggleTool.bind(null, node));
|
||||
yield toggleTool(node);
|
||||
}
|
||||
// Toggle again to reset tool enablement state
|
||||
for (let node of toolNodes) {
|
||||
p = p.then(toggleTool.bind(null, node));
|
||||
yield toggleTool(node);
|
||||
}
|
||||
|
||||
// Test that a tool can still be added when no tabs are present:
|
||||
// Disable all tools
|
||||
for (let node of enabledTools) {
|
||||
p = p.then(toggleTool.bind(null, node));
|
||||
yield toggleTool(node);
|
||||
}
|
||||
// Re-enable the tools which are enabled by default
|
||||
for (let node of enabledTools) {
|
||||
p = p.then(toggleTool.bind(null, node));
|
||||
yield toggleTool(node);
|
||||
}
|
||||
|
||||
// Toggle first, middle, and last tools to ensure that toolbox tabs are
|
||||
@ -173,20 +189,19 @@ function testToggleTools() {
|
||||
middleTool = toolNodes[(toolNodes.length / 2) | 0],
|
||||
lastTool = toolNodes[toolNodes.length - 1];
|
||||
|
||||
p = p.then(toggleTool.bind(null, firstTool))
|
||||
.then(toggleTool.bind(null, firstTool))
|
||||
.then(toggleTool.bind(null, middleTool))
|
||||
.then(toggleTool.bind(null, middleTool))
|
||||
.then(toggleTool.bind(null, lastTool))
|
||||
.then(toggleTool.bind(null, lastTool));
|
||||
|
||||
return p;
|
||||
yield toggleTool(firstTool);
|
||||
yield toggleTool(firstTool);
|
||||
yield toggleTool(middleTool);
|
||||
yield toggleTool(middleTool);
|
||||
yield toggleTool(lastTool);
|
||||
yield toggleTool(lastTool);
|
||||
}
|
||||
|
||||
function toggleTool(node) {
|
||||
function* toggleTool(node) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let toolId = node.getAttribute("id");
|
||||
let onRegistrationChange;
|
||||
if (node.checked) {
|
||||
gDevTools.once("tool-unregistered", checkUnregistered.bind(null, toolId, deferred));
|
||||
} else {
|
||||
@ -195,7 +210,7 @@ function toggleTool(node) {
|
||||
node.scrollIntoView();
|
||||
EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
|
||||
|
||||
return deferred.promise;
|
||||
yield deferred.promise;
|
||||
}
|
||||
|
||||
function checkUnregistered(toolId, deferred, event, data) {
|
||||
@ -249,32 +264,12 @@ function GetPref(name) {
|
||||
}
|
||||
}
|
||||
|
||||
function SetPref(name, value) {
|
||||
let type = Services.prefs.getPrefType(name);
|
||||
switch (type) {
|
||||
case Services.prefs.PREF_STRING:
|
||||
return Services.prefs.setCharPref(name, value);
|
||||
case Services.prefs.PREF_INT:
|
||||
return Services.prefs.setIntPref(name, value);
|
||||
case Services.prefs.PREF_BOOL:
|
||||
return Services.prefs.setBoolPref(name, value);
|
||||
default:
|
||||
throw new Error("Unknown type");
|
||||
function* cleanup() {
|
||||
gDevTools.unregisterTool("test-tool");
|
||||
yield toolbox.destroy();
|
||||
gBrowser.removeCurrentTab();
|
||||
for (let pref of modifiedPrefs) {
|
||||
Services.prefs.clearUserPref(pref);
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
toolbox.destroy().then(function() {
|
||||
gBrowser.removeCurrentTab();
|
||||
for (let pref of modifiedPrefs) {
|
||||
Services.prefs.clearUserPref(pref);
|
||||
}
|
||||
toolbox = doc = panelWin = modifiedPrefs = null;
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
function errorHandler(error) {
|
||||
ok(false, "Unexpected error: " + error);
|
||||
cleanup();
|
||||
toolbox = doc = panelWin = modifiedPrefs = null;
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ OptionsPanel.prototype = {
|
||||
|
||||
let onCheckboxClick = (checkbox) => {
|
||||
let toolDefinition = toggleableButtons.filter(tool => tool.id === checkbox.id)[0];
|
||||
SetPref(toolDefinition.visibilityswitch, checkbox.checked);
|
||||
Services.prefs.setBoolPref(toolDefinition.visibilityswitch, checkbox.checked);
|
||||
setToolboxButtonsVisibility();
|
||||
};
|
||||
|
||||
@ -198,7 +198,7 @@ OptionsPanel.prototype = {
|
||||
let onCheckboxClick = function(id) {
|
||||
let toolDefinition = gDevTools._tools.get(id);
|
||||
// Set the kill switch pref boolean to true
|
||||
SetPref(toolDefinition.visibilityswitch, this.checked);
|
||||
Services.prefs.setBoolPref(toolDefinition.visibilityswitch, this.checked);
|
||||
if (this.checked) {
|
||||
gDevTools.emit("tool-registered", id);
|
||||
}
|
||||
|
@ -58,7 +58,8 @@ const ToolboxButtons = [
|
||||
!target.isAddon && target.activeTab && target.activeTab.traits.frames
|
||||
)
|
||||
},
|
||||
{ id: "command-button-splitconsole" },
|
||||
{ id: "command-button-splitconsole",
|
||||
isTargetSupported: target => !target.isAddon },
|
||||
{ id: "command-button-responsive" },
|
||||
{ id: "command-button-paintflashing" },
|
||||
{ id: "command-button-tilt" },
|
||||
@ -619,11 +620,6 @@ Toolbox.prototype = {
|
||||
this._buildPickerButton();
|
||||
}
|
||||
|
||||
if (!this.target.isLocalTab) {
|
||||
this.setToolboxButtonsVisibility();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let spec = CommandUtils.getCommandbarSpec("devtools.toolbox.toolbarSpec");
|
||||
let environment = CommandUtils.createEnvironment(this, '_target');
|
||||
return CommandUtils.createRequisition(environment).then(requisition => {
|
||||
@ -631,7 +627,11 @@ Toolbox.prototype = {
|
||||
return CommandUtils.createButtons(spec, this.target, this.doc,
|
||||
requisition).then(buttons => {
|
||||
let container = this.doc.getElementById("toolbox-buttons");
|
||||
buttons.forEach(container.appendChild.bind(container));
|
||||
buttons.forEach(button=> {
|
||||
if (button) {
|
||||
container.appendChild(button);
|
||||
}
|
||||
});
|
||||
this.setToolboxButtonsVisibility();
|
||||
});
|
||||
});
|
||||
@ -875,18 +875,49 @@ Toolbox.prototype = {
|
||||
iframe.tooltip = "aHTMLTooltip";
|
||||
iframe.style.visibility = "hidden";
|
||||
|
||||
let vbox = this.doc.getElementById("toolbox-panel-" + id);
|
||||
vbox.appendChild(iframe);
|
||||
gDevTools.emit(id + "-init", this, iframe);
|
||||
this.emit(id + "-init", iframe);
|
||||
|
||||
// If no parent yet, append the frame into default location.
|
||||
if (!iframe.parentNode) {
|
||||
let vbox = this.doc.getElementById("toolbox-panel-" + id);
|
||||
vbox.appendChild(iframe);
|
||||
}
|
||||
|
||||
let onLoad = () => {
|
||||
// Prevent flicker while loading by waiting to make visible until now.
|
||||
iframe.style.visibility = "visible";
|
||||
|
||||
// The build method should return a panel instance, so events can
|
||||
// be fired with the panel as an argument. However, in order to keep
|
||||
// backward compatibility with existing extensions do a check
|
||||
// for a promise return value.
|
||||
let built = definition.build(iframe.contentWindow, this);
|
||||
if (!(built instanceof Promise)) {
|
||||
let panel = built;
|
||||
iframe.panel = panel;
|
||||
|
||||
gDevTools.emit(id + "-build", this, panel);
|
||||
this.emit(id + "-build", panel);
|
||||
|
||||
// The panel can implement an 'open' method for asynchronous
|
||||
// initialization sequence.
|
||||
if (typeof panel.open == "function") {
|
||||
built = panel.open();
|
||||
} else {
|
||||
let deferred = promise.defer();
|
||||
deferred.resolve(panel);
|
||||
built = deferred.promise;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait till the panel is fully ready and fire 'ready' events.
|
||||
promise.resolve(built).then((panel) => {
|
||||
this._toolPanels.set(id, panel);
|
||||
this.emit(id + "-ready", panel);
|
||||
|
||||
gDevTools.emit(id + "-ready", this, panel);
|
||||
this.emit(id + "-ready", panel);
|
||||
|
||||
deferred.resolve(panel);
|
||||
}, console.error);
|
||||
};
|
||||
@ -1514,7 +1545,7 @@ Toolbox.prototype = {
|
||||
// Remove the host UI
|
||||
outstanding.push(this.destroyHost());
|
||||
|
||||
if (this.target.isLocalTab) {
|
||||
if (this._requisition) {
|
||||
this._requisition.destroy();
|
||||
}
|
||||
this._telemetry.toolClosed("toolbox");
|
||||
|
@ -82,8 +82,7 @@ Tools.options = {
|
||||
return true;
|
||||
},
|
||||
build: function(iframeWindow, toolbox) {
|
||||
let panel = new OptionsPanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
return new OptionsPanel(iframeWindow, toolbox);
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,8 +114,7 @@ Tools.webConsole = {
|
||||
return true;
|
||||
},
|
||||
build: function(iframeWindow, toolbox) {
|
||||
let panel = new WebConsolePanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
return new WebConsolePanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
@ -149,8 +147,7 @@ Tools.inspector = {
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
let panel = new InspectorPanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
return new InspectorPanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
@ -175,8 +172,7 @@ Tools.jsdebugger = {
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
let panel = new DebuggerPanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
return new DebuggerPanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
@ -200,8 +196,7 @@ Tools.styleEditor = {
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
let panel = new StyleEditorPanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
return new StyleEditorPanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
@ -221,8 +216,7 @@ Tools.shaderEditor = {
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
let panel = new ShaderEditorPanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
return new ShaderEditorPanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
@ -242,8 +236,7 @@ Tools.canvasDebugger = {
|
||||
return !target.isAddon && !target.chrome;
|
||||
},
|
||||
build: function (iframeWindow, toolbox) {
|
||||
let panel = new CanvasDebuggerPanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
return new CanvasDebuggerPanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
@ -261,8 +254,7 @@ Tools.webAudioEditor = {
|
||||
return !target.isAddon;
|
||||
},
|
||||
build: function(iframeWindow, toolbox) {
|
||||
let panel = new WebAudioEditorPanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
return new WebAudioEditorPanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
@ -288,8 +280,7 @@ Tools.jsprofiler = {
|
||||
},
|
||||
|
||||
build: function (frame, target) {
|
||||
let panel = new ProfilerPanel(frame, target);
|
||||
return panel.open();
|
||||
return new ProfilerPanel(frame, target);
|
||||
}
|
||||
};
|
||||
|
||||
@ -314,8 +305,7 @@ Tools.netMonitor = {
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
let panel = new NetMonitorPanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
return new NetMonitorPanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
@ -341,8 +331,7 @@ Tools.storage = {
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
let panel = new StoragePanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
return new StoragePanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
@ -364,8 +353,7 @@ Tools.scratchpad = {
|
||||
},
|
||||
|
||||
build: function(iframeWindow, toolbox) {
|
||||
let panel = new ScratchpadPanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
return new ScratchpadPanel(iframeWindow, toolbox);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2187,6 +2187,7 @@ function truncateString(str, maxLength) {
|
||||
"…" +
|
||||
str.substring(str.length - Math.floor(maxLength / 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse attribute names and values from a string.
|
||||
*
|
||||
@ -2200,49 +2201,16 @@ function truncateString(str, maxLength) {
|
||||
function parseAttributeValues(attr, doc) {
|
||||
attr = attr.trim();
|
||||
|
||||
// Prepare other versions of the string to be parsed by appending a " or '
|
||||
// and using those if the first one fails to parse without these characters
|
||||
let stringsToParse = [
|
||||
"<div " + attr + "></div>",
|
||||
"<div " + attr + "\"></div>",
|
||||
"<div " + attr + "'></div>"
|
||||
];
|
||||
|
||||
// Try to parse as XML, this way, if the string is wellformed, this will
|
||||
// preserve the case.
|
||||
let parsedAttributes = [];
|
||||
for (let str of stringsToParse) {
|
||||
let parsed = DOMParser.parseFromString(str, "text/xml");
|
||||
// The XML parser generates a valid XML document even when parsing errors
|
||||
// occur, in which case the document contains a <parsererror> node, so check
|
||||
// that the document contains our expected DIV
|
||||
if (parsed.childNodes[0].localName === "div") {
|
||||
for (let {name, value} of parsed.childNodes[0].attributes) {
|
||||
parsedAttributes.push({ name, value });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the XML parsing failed, parse as HTML to get malformed attributes
|
||||
if (parsedAttributes.length === 0) {
|
||||
for (let str of stringsToParse) {
|
||||
let parsed = DOMParser.parseFromString(str, "text/html");
|
||||
// Check that the parsed document does contain the expected DIV as a child
|
||||
// of <body>
|
||||
if (parsed.body.childNodes[0]) {
|
||||
for (let {name, value} of parsed.body.childNodes[0].attributes) {
|
||||
parsedAttributes.push({ name, value });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle bad user inputs by appending a " or ' if it fails to parse without
|
||||
// them. Also note that a SVG tag is used to make sure the HTML parser
|
||||
// preserves mixed-case attributes
|
||||
let el = DOMParser.parseFromString("<svg " + attr + "></svg>", "text/html").body.childNodes[0] ||
|
||||
DOMParser.parseFromString("<svg " + attr + "\"></svg>", "text/html").body.childNodes[0] ||
|
||||
DOMParser.parseFromString("<svg " + attr + "'></svg>", "text/html").body.childNodes[0];
|
||||
|
||||
let div = doc.createElement("div");
|
||||
|
||||
let attributes = [];
|
||||
for (let {name, value} of parsedAttributes) {
|
||||
for (let {name, value} of el.attributes) {
|
||||
// Try to set on an element in the document, throws exception on bad input.
|
||||
// Prevents InvalidCharacterError - "String contains an invalid character".
|
||||
try {
|
||||
|
@ -45,11 +45,8 @@ function* testWellformedMixedCase(inspector) {
|
||||
}
|
||||
|
||||
function* testMalformedMixedCase(inspector) {
|
||||
info("Modifying a mixed-case attribute, making sure to generate a parsing" +
|
||||
"error, and expecting the attribute's case to NOT be preserved");
|
||||
// See /browser/devtools/markupview/markup-view.js:parseAttributeValues
|
||||
// When attributes are malformed, they cannot be parsed with the XML parser
|
||||
// and so we fall back to the HTML parser which lowercases attributes.
|
||||
info("Modifying a malformed, mixed-case attribute, " +
|
||||
"expecting the attribute's case to be preserved");
|
||||
|
||||
info("Listening to markup mutations");
|
||||
let onMutated = inspector.once("markupmutation");
|
||||
@ -67,7 +64,7 @@ function* testMalformedMixedCase(inspector) {
|
||||
yield onMutated;
|
||||
|
||||
assertAttributes("svg", {
|
||||
"viewbox": "<>",
|
||||
"viewBox": "<>",
|
||||
"width": "200",
|
||||
"height": "200"
|
||||
});
|
||||
|
@ -1212,9 +1212,6 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
this.updateMenuView(template, 'method', aMethod);
|
||||
this.updateMenuView(template, 'url', aUrl);
|
||||
|
||||
let waterfall = $(".requests-menu-waterfall", template);
|
||||
waterfall.style.backgroundImage = this._cachedWaterfallBackground;
|
||||
|
||||
// Flatten the DOM by removing one redundant box (the template container).
|
||||
for (let node of template.childNodes) {
|
||||
fragment.appendChild(node.cloneNode(true));
|
||||
@ -1378,7 +1375,6 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
// Redraw and set the canvas background for each waterfall view.
|
||||
this._showWaterfallDivisionLabels(scale);
|
||||
this._drawWaterfallBackground(scale);
|
||||
this._flushWaterfallBackgrounds();
|
||||
|
||||
// Apply CSS transforms to each waterfall in this container totalTime
|
||||
// accurately translate and resize as needed.
|
||||
@ -1497,8 +1493,8 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
let pixelArray = imageData.data;
|
||||
|
||||
let buf = new ArrayBuffer(pixelArray.length);
|
||||
let buf8 = new Uint8ClampedArray(buf);
|
||||
let data32 = new Uint32Array(buf);
|
||||
let view8bit = new Uint8ClampedArray(buf);
|
||||
let view32bit = new Uint32Array(buf);
|
||||
|
||||
// Build new millisecond tick lines...
|
||||
let timingStep = REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE;
|
||||
@ -1520,26 +1516,16 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
let increment = scaledStep * Math.pow(2, i);
|
||||
for (let x = 0; x < canvasWidth; x += increment) {
|
||||
let position = (window.isRTL ? canvasWidth - x : x) | 0;
|
||||
data32[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
|
||||
view32bit[position] = (alphaComponent << 24) | (b << 16) | (g << 8) | r;
|
||||
}
|
||||
alphaComponent += REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush the image data and cache the waterfall background.
|
||||
pixelArray.set(buf8);
|
||||
pixelArray.set(view8bit);
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
this._cachedWaterfallBackground = "url(" + canvas.toDataURL() + ")";
|
||||
},
|
||||
|
||||
/**
|
||||
* Reapplies the current waterfall background on all request items.
|
||||
*/
|
||||
_flushWaterfallBackgrounds: function() {
|
||||
for (let { target } of this) {
|
||||
let waterfallNode = $(".requests-menu-waterfall", target);
|
||||
waterfallNode.style.backgroundImage = this._cachedWaterfallBackground;
|
||||
}
|
||||
document.mozSetImageElement("waterfall-background", canvas);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1762,7 +1748,6 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
_canvas: null,
|
||||
_ctx: null,
|
||||
_cachedWaterfallWidth: 0,
|
||||
_cachedWaterfallBackground: "",
|
||||
_firstRequestStartedMillis: -1,
|
||||
_lastRequestEndedMillis: -1,
|
||||
_updateQueue: [],
|
||||
|
@ -1,4 +1,5 @@
|
||||
[DEFAULT]
|
||||
skip-if = e10s # Bug 1058898
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
doc_simple-test.html
|
||||
|
@ -104,6 +104,13 @@ let CommandUtils = {
|
||||
if (command == null) {
|
||||
throw new Error("No command '" + typed + "'");
|
||||
}
|
||||
|
||||
// Do not build a button for a non-remote safe command in a non-local target.
|
||||
if (!target.isLocalTab && !command.isRemoteSafe) {
|
||||
requisition.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.buttonId != null) {
|
||||
button.id = command.buttonId;
|
||||
if (command.buttonClass != null) {
|
||||
|
@ -14,6 +14,7 @@ exports.items = [
|
||||
buttonId: "command-button-splitconsole",
|
||||
buttonClass: "command-button command-button-invertable",
|
||||
tooltipText: gcli.lookup("splitconsoleTooltip"),
|
||||
isRemoteSafe: true,
|
||||
state: {
|
||||
isChecked: function(target) {
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
|
@ -2,8 +2,8 @@
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
addons/simulators.json
|
||||
doc_tabs.html
|
||||
head.js
|
||||
templates.json
|
||||
|
||||
[browser_tabs.js]
|
||||
skip-if = true # Fails on TBPL, to be fixed in bug 1062611
|
||||
|
@ -2,6 +2,8 @@
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webide/test/doc_tabs.html";
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
SimpleTest.requestCompleteLog();
|
||||
@ -9,10 +11,14 @@ function test() {
|
||||
Task.spawn(function() {
|
||||
const { DebuggerServer } =
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
|
||||
// Since we test the connections set below, destroy the server in case it
|
||||
// was left open.
|
||||
DebuggerServer.destroy();
|
||||
DebuggerServer.init(function () { return true; });
|
||||
DebuggerServer.addBrowserActors();
|
||||
|
||||
let tab = yield addTab("about:newtab");
|
||||
let tab = yield addTab(TEST_URI);
|
||||
|
||||
let win = yield openWebIDE();
|
||||
|
||||
@ -23,15 +29,13 @@ function test() {
|
||||
yield selectTabProject(win);
|
||||
|
||||
let project = win.AppManager.selectedProject;
|
||||
is(project.location, "about:newtab", "Location is correct");
|
||||
is(project.name, "New Tab", "Name is correct");
|
||||
is(project.location, TEST_URI, "Location is correct");
|
||||
is(project.name, "example.com: Test Tab", "Name is correct");
|
||||
|
||||
yield closeWebIDE(win);
|
||||
DebuggerServer.destroy();
|
||||
yield removeTab(tab);
|
||||
|
||||
finish();
|
||||
});
|
||||
}).then(finish, handleError);
|
||||
}
|
||||
|
||||
function connectToLocal(win) {
|
||||
|
15
browser/devtools/webide/test/doc_tabs.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Test Tab</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
Test Tab
|
||||
</body>
|
||||
|
||||
</html>
|
@ -153,3 +153,8 @@ function removeTab(aTab, aWindow) {
|
||||
targetBrowser.removeTab(aTab);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function handleError(aError) {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
finish();
|
||||
}
|
||||
|
@ -67,8 +67,7 @@ window.busy-determined #action-busy-undetermined {
|
||||
|
||||
#project-panel-button {
|
||||
-moz-box-pack: start;
|
||||
width: 150px;
|
||||
max-width: 150px;
|
||||
max-width: calc(50vw - 100px);
|
||||
}
|
||||
|
||||
#project-panel-button > .panel-button-image {
|
||||
@ -88,8 +87,7 @@ window.busy-determined #action-busy-undetermined {
|
||||
}
|
||||
|
||||
#project-panel-button > .panel-button-label {
|
||||
width: 150px;
|
||||
max-width: 150px;
|
||||
-moz-box-flex: 1;
|
||||
}
|
||||
|
||||
/* Panel buttons - runtime */
|
||||
|
@ -71,6 +71,17 @@ object with the following properties:
|
||||
- ``move`` counts the number of times an item is moved somewhere else (but not to the palette);
|
||||
- ``remove`` counts the number of times an item is removed to the palette;
|
||||
- ``reset`` counts the number of times the 'restore defaults' button is used;
|
||||
- ``search`` is an object tracking searches of various types, keyed off the search
|
||||
location, storing a number indicating how often the respective type of search
|
||||
has happened.
|
||||
- There are also two special keys that mean slightly different things.
|
||||
- ``urlbar-keyword`` records searches that would have been an invalid-protocol
|
||||
error, but are now keyword searches. They are also counted in the ``urlbar``
|
||||
keyword (along with all the other urlbar searches).
|
||||
- ``selection`` searches records selections of search suggestions. They include
|
||||
the source, the index of the selection, and the kind of selection (mouse or
|
||||
enter key). Selection searches are also counted in their sources.
|
||||
|
||||
|
||||
|
||||
``UITour``
|
||||
|
@ -66,6 +66,33 @@
|
||||
<!ENTITY cmd.clearList.accesskey "a">
|
||||
<!ENTITY cmd.clearDownloads.label "Clear Downloads">
|
||||
<!ENTITY cmd.clearDownloads.accesskey "D">
|
||||
<!-- LOCALIZATION NOTE (cmd.unblock.label):
|
||||
This command may be shown in the context menu, as a menu button item, or as
|
||||
a text link when malware or potentially unwanted downloads are blocked.
|
||||
Note: This string doesn't exist in the UI yet. See bug 1053890.
|
||||
-->
|
||||
<!ENTITY cmd.unblock.label "Unblock">
|
||||
<!ENTITY cmd.unblock.accesskey "U">
|
||||
<!-- LOCALIZATION NOTE (cmd.removeFile.label):
|
||||
This command may be shown in the context menu or as a menu button label
|
||||
when malware or potentially unwanted downloads are blocked.
|
||||
Note: This string doesn't exist in the UI yet. See bug 1053890.
|
||||
-->
|
||||
<!ENTITY cmd.removeFile.label "Remove File">
|
||||
<!ENTITY cmd.removeFile.accesskey "m">
|
||||
|
||||
<!-- LOCALIZATION NOTE (blocked.label):
|
||||
Shown as a tag before the file name for some types of blocked downloads.
|
||||
Note: This string doesn't exist in the UI yet. See bug 1053890.
|
||||
-->
|
||||
<!ENTITY blocked.label "BLOCKED">
|
||||
|
||||
<!-- LOCALIZATION NOTE (learnMore.label):
|
||||
Shown as a text link for some types of blocked downloads, for example
|
||||
malware, when there is an associated explanation page on the Mozilla site.
|
||||
Note: This string doesn't exist in the UI yet. See bug 1053890.
|
||||
-->
|
||||
<!ENTITY learnMore.label "Learn More">
|
||||
|
||||
<!-- LOCALIZATION NOTE (downloadsHistory.label, downloadsHistory.accesskey):
|
||||
This string is shown at the bottom of the Downloads Panel when all the
|
||||
|
@ -38,13 +38,40 @@ stateBlockedPolicy=Blocked by your security zone policy
|
||||
# Indicates that the download was blocked after scanning.
|
||||
stateDirty=Blocked: May contain a virus or spyware
|
||||
|
||||
# LOCALIZATION NOTE (blockedMalware, blockedPotentiallyUnwanted,
|
||||
# blockedUncommon):
|
||||
# These strings are shown in the panel for some types of blocked downloads, and
|
||||
# are immediately followed by the "Learn More" link, thus they must end with a
|
||||
# period. You may need to adjust "downloadDetails.width" in "downloads.dtd" if
|
||||
# this turns out to be longer than the other existing status strings.
|
||||
# Note: These strings don't exist in the UI yet. See bug 1053890.
|
||||
blockedMalware=This file contains a virus or malware.
|
||||
blockedPotentiallyUnwanted=This file may harm your computer.
|
||||
blockedUncommon=This file may not be safe to open.
|
||||
|
||||
# LOCALIZATION NOTE (unblockHeader, unblockTypeMalware,
|
||||
# unblockTypePotentiallyUnwanted, unblockTypeUncommon,
|
||||
# unblockTip, unblockButtonContinue, unblockButtonCancel):
|
||||
# These strings are displayed in the dialog shown when the user asks a blocked
|
||||
# download to be unblocked. The severity of the threat is expressed in
|
||||
# descending order by the unblockType strings, it is higher for files detected
|
||||
# as malware and lower for uncommon downloads.
|
||||
# Note: These strings don't exist in the UI yet. See bug 1053890.
|
||||
unblockHeader=Are you sure you want to unblock this file?
|
||||
unblockTypeMalware=This file contains a virus or other malware that will harm your computer.
|
||||
unblockTypePotentiallyUnwanted=This file, disguised as a helpful download, will make unexpected changes to your programs and settings.
|
||||
unblockTypeUncommon=This file has been downloaded from an unfamiliar and potentially dangerous website and may not be safe to open.
|
||||
unblockTip=You can search for an alternate download source or try to download the file again later.
|
||||
unblockButtonContinue=Unblock anyway
|
||||
unblockButtonCancel=Keep me safe
|
||||
|
||||
# LOCALIZATION NOTE (sizeWithUnits):
|
||||
# %1$S is replaced with the size number, and %2$S with the measurement unit.
|
||||
sizeWithUnits=%1$S %2$S
|
||||
sizeUnknown=Unknown size
|
||||
|
||||
# LOCALIZATION NOTE (shortTimeLeftSeconds, shortTimeLeftMinutes,
|
||||
# shortTimeLeftHours, shortTimeLeftDays):
|
||||
# shortTimeLeftHours, shortTimeLeftDays):
|
||||
# These values are displayed in the downloads indicator in the main browser
|
||||
# window, where space is available for three characters maximum. %1$S is
|
||||
# replaced with the time left for the given measurement unit. Even for days,
|
||||
|
@ -191,7 +191,7 @@ let AboutHome = {
|
||||
|
||||
let engine = Services.search.currentEngine;
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
window.BrowserSearch.recordSearchInHealthReport(engine, "abouthome");
|
||||
window.BrowserSearch.recordSearchInHealthReport(engine, "abouthome", data.selection);
|
||||
#endif
|
||||
// Trigger a search through nsISearchEngine.getSubmission()
|
||||
let submission = engine.getSubmission(data.searchTerms, null, "homepage");
|
||||
|
@ -179,6 +179,7 @@ this.BrowserUITelemetry = {
|
||||
|
||||
Services.obs.addObserver(this, "sessionstore-windows-restored", false);
|
||||
Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
|
||||
Services.obs.addObserver(this, "autocomplete-did-enter-text", false);
|
||||
CustomizableUI.addListener(this);
|
||||
},
|
||||
|
||||
@ -190,6 +191,13 @@ this.BrowserUITelemetry = {
|
||||
case "browser-delayed-startup-finished":
|
||||
this._registerWindow(aSubject);
|
||||
break;
|
||||
case "autocomplete-did-enter-text":
|
||||
let input = aSubject.QueryInterface(Ci.nsIAutoCompleteInput);
|
||||
if (input && input.id == "urlbar" && !input.inPrivateContext &&
|
||||
input.popup.selectedIndex != -1) {
|
||||
this._logAwesomeBarSearchResult(input.textValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@ -554,11 +562,25 @@ this.BrowserUITelemetry = {
|
||||
this._countEvent(["customize", aEventType]);
|
||||
},
|
||||
|
||||
countSearchEvent: function(source, query) {
|
||||
countSearchEvent: function(source, query, selection) {
|
||||
this._countEvent(["search", source]);
|
||||
if ((/^[a-zA-Z]+:[^\/\\]/).test(query)) {
|
||||
this._countEvent(["search", "urlbar-keyword"]);
|
||||
}
|
||||
if (selection) {
|
||||
this._countEvent(["search", "selection", source, selection.index, selection.kind]);
|
||||
}
|
||||
},
|
||||
|
||||
_logAwesomeBarSearchResult: function (url) {
|
||||
let spec = Services.search.parseSubmissionURL(url);
|
||||
if (spec.engine) {
|
||||
let matchedEngine = "default";
|
||||
if (spec.engine.name !== Services.search.currentEngine.name) {
|
||||
matchedEngine = "other";
|
||||
}
|
||||
this.countSearchEvent("autocomplete-" + matchedEngine);
|
||||
}
|
||||
},
|
||||
|
||||
_durations: {
|
||||
|
@ -206,7 +206,7 @@ this.ContentSearch = {
|
||||
]);
|
||||
let browserWin = msg.target.ownerDocument.defaultView;
|
||||
let engine = Services.search.getEngineByName(data.engineName);
|
||||
browserWin.BrowserSearch.recordSearchInHealthReport(engine, data.whence);
|
||||
browserWin.BrowserSearch.recordSearchInHealthReport(engine, data.whence, data.selection);
|
||||
let submission = engine.getSubmission(data.searchString, "", data.whence);
|
||||
browserWin.loadURI(submission.uri.spec, null, submission.postData);
|
||||
return Promise.resolve();
|
||||
|
@ -320,7 +320,10 @@ label.requests-menu-status-code {
|
||||
.requests-menu-subitem.requests-menu-waterfall {
|
||||
-moz-padding-start: 0px;
|
||||
-moz-padding-end: 4px;
|
||||
background-repeat: repeat-y; /* Background created on a <canvas> in js. */
|
||||
/* Background created on a <canvas> in js. */
|
||||
/* @see browser/devtools/netmonitor/netmonitor-view.js */
|
||||
background-image: -moz-element(#waterfall-background);
|
||||
background-repeat: repeat-y;
|
||||
background-position: -1px center;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 615 B After Width: | Height: | Size: 577 B |
Before Width: | Height: | Size: 641 B After Width: | Height: | Size: 590 B |
Before Width: | Height: | Size: 222 B After Width: | Height: | Size: 136 B |
Before Width: | Height: | Size: 609 B After Width: | Height: | Size: 557 B |
Before Width: | Height: | Size: 455 B After Width: | Height: | Size: 409 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 971 B |
Before Width: | Height: | Size: 798 B After Width: | Height: | Size: 789 B |
Before Width: | Height: | Size: 800 B After Width: | Height: | Size: 799 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 791 B After Width: | Height: | Size: 140 B |
Before Width: | Height: | Size: 776 B After Width: | Height: | Size: 142 B |
Before Width: | Height: | Size: 767 B After Width: | Height: | Size: 152 B |
Before Width: | Height: | Size: 352 B After Width: | Height: | Size: 307 B |
Before Width: | Height: | Size: 425 B After Width: | Height: | Size: 421 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 462 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 550 B After Width: | Height: | Size: 540 B |
Before Width: | Height: | Size: 118 B After Width: | Height: | Size: 115 B |