Merge m-c to inbound. a=merge

This commit is contained in:
Ryan VanderMeulen 2014-09-09 19:29:28 -04:00
commit 9a0967f21f
296 changed files with 2641 additions and 2313 deletions

View 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

View File

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

View File

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

View File

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

View File

@ -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_\-]+$')

View File

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

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

View File

@ -0,0 +1,6 @@
{
"id": "test-developers@jetpack",
"title": "Test developers package key",
"author": "Erik Vold",
"developers": [ "A", "B" ]
}

View File

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

View File

@ -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 = "&lt;span&gt;b&lt;b&gt;a&lt;/b&gt;r&lt;/span&gt;";
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 = "&lt;span&gt;b&lt;b&gt;a&lt;/b&gt;r&lt;/span&gt;";
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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "d43b2e2dba496f624d62c1b72a22dc5e34953fbd",
"revision": "7c8bbd0eb4543c729b029339cb79bfaa4ec23069",
"repo_path": "/integration/gaia-central"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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}>
&laquo;&nbsp;{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
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: [],

View File

@ -1,4 +1,5 @@
[DEFAULT]
skip-if = e10s # Bug 1058898
subsuite = devtools
support-files =
doc_simple-test.html

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 615 B

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 641 B

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 B

After

Width:  |  Height:  |  Size: 136 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 609 B

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 B

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 800 B

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 791 B

After

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 776 B

After

Width:  |  Height:  |  Size: 142 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 767 B

After

Width:  |  Height:  |  Size: 152 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 B

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 425 B

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 B

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 B

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

After

Width:  |  Height:  |  Size: 115 B

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