Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-01-20 15:36:47 +01:00
commit 0219a9d227
226 changed files with 26531 additions and 2985 deletions

View File

@ -73,7 +73,6 @@ browser/components/downloads/**
browser/components/feeds/**
browser/components/migration/**
browser/components/*.js
browser/components/places/**
browser/components/pocket/**
browser/components/preferences/**
browser/components/privatebrowsing/**
@ -162,7 +161,6 @@ toolkit/modules/tests/xpcshell/test_task.js
# Not yet updated
toolkit/components/osfile/**
toolkit/components/passwordmgr/**
toolkit/components/places/**
# Uses preprocessing
toolkit/content/contentAreaUtils.js

View File

@ -10,7 +10,7 @@ module.metadata = {
const { Cc, Ci } = require('chrome');
const events = require('../system/events');
const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser,
getMostRecentBrowserWindow, getMostRecentWindow } = require('../window/utils');
getMostRecentBrowserWindow, getToplevelWindow, getMostRecentWindow } = require('../window/utils');
const { deprecateFunction } = require('../util/deprecate');
const { ignoreWindow } = require('sdk/private-browsing/utils');
const { isPrivateBrowsingSupported } = require('../self');
@ -127,7 +127,7 @@ WindowTracker.prototype = {
if (event.type == 'load' && event.target) {
var window = event.target.defaultView;
if (window)
this._regWindow(window);
this._regWindow(getToplevelWindow(window));
}
}
catch(e) {
@ -136,7 +136,7 @@ WindowTracker.prototype = {
},
_onToplevelWindowReady: function _onToplevelWindowReady({subject}) {
let window = subject;
let window = getToplevelWindow(subject);
// ignore private windows if they are not supported
if (ignoreWindow(window))
return;

View File

@ -6,7 +6,7 @@
const { Class } = require('../core/heritage');
const { observer } = require('./observer');
const { isBrowser, getMostRecentBrowserWindow, windows, open, getInnerId,
getWindowTitle, isFocused, isWindowPrivate } = require('../window/utils');
getWindowTitle, getToplevelWindow, isFocused, isWindowPrivate } = require('../window/utils');
const { List, addListItem, removeListItem } = require('../util/list');
const { viewFor } = require('../view/core');
const { modelFor } = require('../model/core');
@ -189,14 +189,16 @@ for (let domWindow of windows()) {
}
var windowEventListener = (event, domWindow, ...args) => {
if (ignoreWindow(domWindow))
let toplevelWindow = getToplevelWindow(domWindow);
if (ignoreWindow(toplevelWindow))
return;
let window = modelsFor.get(domWindow);
let window = modelsFor.get(toplevelWindow);
if (!window)
window = makeNewWindow(domWindow);
window = makeNewWindow(toplevelWindow);
if (isBrowser(domWindow)) {
if (isBrowser(toplevelWindow)) {
if (event == "open")
addListItem(browserWindows, window);
else if (event == "close")
@ -208,7 +210,7 @@ var windowEventListener = (event, domWindow, ...args) => {
// The window object shouldn't be reachable after closed
if (event == "close") {
viewsFor.delete(window);
modelsFor.delete(domWindow);
modelsFor.delete(toplevelWindow);
}
};
observer.on("*", windowEventListener);

View File

@ -557,8 +557,24 @@ const resolveURI = iced(function resolveURI(id, mapping) {
while (index < count) {
let [ path, uri ] = mapping[index++];
if (id.indexOf(path) === 0)
// Strip off any trailing slashes to make comparisons simpler
let stripped = path.endsWith('/') ? path.slice(0, -1) : path;
// We only want to match path segments explicitly. Examples:
// * "foo/bar" matches for "foo/bar"
// * "foo/bar" matches for "foo/bar/baz"
// * "foo/bar" does not match for "foo/bar-1"
// * "foo/bar/" does not match for "foo/bar"
// * "foo/bar/" matches for "foo/bar/baz"
//
// Check for an empty path, an exact match, or a substring match
// with the next character being a forward slash.
if(stripped === "" ||
(id.indexOf(stripped) === 0 &&
(id.length === path.length || id[stripped.length] === '/'))) {
return normalizeExt(id.replace(path, uri));
}
}
return void 0; // otherwise we raise a warning, see bug 910304
});

View File

@ -22,6 +22,7 @@ const DISABLE_POPUP_PREF = 'dom.disable_open_during_load';
const fixtures = require("../fixtures");
const { base64jpeg } = fixtures;
const { cleanUI, before, after } = require("sdk/test/utils");
const { wait } = require('../event/helpers');
// Bug 682681 - tab.title should never be empty
exports.testBug682681_aboutURI = function(assert, done) {
@ -1214,32 +1215,35 @@ exports['test active tab properties defined on popup closed'] = function (assert
});
};
// related to bug 922956
// related to bugs 922956 and 989288
// https://bugzilla.mozilla.org/show_bug.cgi?id=922956
exports["test ready event after window.open"] = function (assert, done) {
// https://bugzilla.mozilla.org/show_bug.cgi?id=989288
exports["test tabs ready and close after window.open"] = function*(assert, done) {
// ensure popups open in a new window and disable popup blocker
setPref(OPEN_IN_NEW_WINDOW_PREF, 2);
setPref(DISABLE_POPUP_PREF, false);
let firstRun = true;
tabs.on('ready', function onReady(tab) {
if (firstRun) {
assert.pass("tab ready callback after 1st window.open");
firstRun = false;
tab.close();
}
else {
assert.pass("tab ready callback after 2nd window.open");
tabs.removeListener('ready', onReady);
tab.close(done);
}
});
// open windows to trigger observers
tabs.activeTab.attach({
contentScript: "window.open('about:blank');" +
"window.open('about:blank', '', " +
"'width=800,height=600,resizable=no,status=no,location=no');"
});
}
let tab1 = yield wait(tabs, "ready");
assert.pass("first tab ready has occured");
let tab2 = yield wait(tabs, "ready");
assert.pass("second tab ready has occured");
tab1.close();
yield wait(tabs, "close");
assert.pass("first tab close has occured");
tab2.close();
yield wait(tabs, "close");
assert.pass("second tab close has occured");
};
// related to bug #939496
exports["test tab open event for new window"] = function(assert, done) {

View File

@ -387,6 +387,19 @@ exports["test require#resolve"] = function(assert) {
assert.equal(foundRoot + "sdk/tabs.js", require.resolve("sdk/tabs"), "correct resolution of sdk module");
assert.equal(foundRoot + "toolkit/loader.js", require.resolve("toolkit/loader"), "correct resolution of sdk module");
const localLoader = Loader({
paths: { "foo/bar": "bizzle",
"foo/bar2/": "bizzle2",
// Just to make sure this doesn't match the first entry,
// let use resolve this module
"foo/bar-bar": "foo/bar-bar" }
});
const localRequire = Require(localLoader, module);
assert.equal(localRequire.resolve("foo/bar"), "bizzle.js");
assert.equal(localRequire.resolve("foo/bar/baz"), "bizzle/baz.js");
assert.equal(localRequire.resolve("foo/bar-bar"), "foo/bar-bar.js");
assert.equal(localRequire.resolve("foo/bar2/"), "bizzle2.js");
};
const modulesURI = require.resolve("toolkit/loader").replace("toolkit/loader.js", "");

View File

@ -586,6 +586,25 @@ exports.testRelatedTab = function(assert, done) {
});
};
// related to bug #989288
// https://bugzilla.mozilla.org/show_bug.cgi?id=989288
exports.testRelatedTabNewWindow = function(assert, done) {
let url = "about:logo"
let pageMod = new PageMod({
include: url,
onAttach: function(worker) {
assert.equal(worker.tab.url, url, "Worker.tab.url is valid");
worker.tab.close(done);
}
});
tabs.activeTab.attach({
contentScript: "window.open('about:logo', '', " +
"'width=800,height=600,resizable=no,status=no,location=no');"
});
};
exports.testRelatedTabNoRequireTab = function(assert, done) {
let loader = Loader(module);
let tab;

View File

@ -20,6 +20,10 @@ const { after } = require("sdk/test/utils");
const { merge } = require("sdk/util/object");
const self = require("sdk/self");
const { openTab } = require("../tabs/utils");
const { set: setPref, reset: resetPref } = require("sdk/preferences/service");
const OPEN_IN_NEW_WINDOW_PREF = 'browser.link.open_newwindow';
const DISABLE_POPUP_PREF = 'dom.disable_open_during_load';
const { wait } = require('../event/helpers');
// TEST: open & close window
exports.testOpenAndCloseWindow = function(assert, done) {
@ -583,8 +587,35 @@ exports.testWindowTabEventBindings = function(assert, done) {
windowTabs.open(openArgs);
}
// related to bug #989288
// https://bugzilla.mozilla.org/show_bug.cgi?id=989288
exports["test window events after window.open"] = function*(assert, done) {
// ensure popups open in a new windows and disable popup blocker
setPref(OPEN_IN_NEW_WINDOW_PREF, 2);
setPref(DISABLE_POPUP_PREF, false);
// open window to trigger observers
tabs.activeTab.attach({
contentScript: "window.open('about:blank', '', " +
"'width=800,height=600,resizable=no,status=no,location=no');"
});
let window = yield wait(browserWindows, "open");
assert.pass("tab open has occured");
window.close();
yield wait(browserWindows,"close");
assert.pass("tab close has occured");
};
after(exports, function*(name, assert) {
resetPopupPrefs();
yield cleanUI();
});
const resetPopupPrefs = () => {
resetPref(OPEN_IN_NEW_WINDOW_PREF);
resetPref(DISABLE_POPUP_PREF);
};
require('sdk/test').run(exports);

View File

@ -1064,6 +1064,9 @@ pref("services.mobileid.server.uri", "https://msisdn.services.mozilla.com");
pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
// Disable Firefox Accounts device registration until bug 1238895 is fixed.
pref("identity.fxaccounts.skipDeviceRegistration", true);
// Enable mapped array buffer.
#ifndef XP_WIN
pref("dom.mapped_arraybuffer.enabled", true);

View File

@ -24,6 +24,7 @@ const ORIGINAL_SENDCUSTOM = SystemAppProxy._sendCustomEvent;
do_register_cleanup(function() {
Services.prefs.setCharPref("identity.fxaccounts.auth.uri", ORIGINAL_AUTH_URI);
SystemAppProxy._sendCustomEvent = ORIGINAL_SENDCUSTOM;
Services.prefs.clearUserPref("identity.fxaccounts.skipDeviceRegistration");
});
// Make profile available so that fxaccounts can store user data
@ -39,6 +40,9 @@ function run_test() {
}
add_task(function test_overall() {
// FxA device registration throws from this context
Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
do_check_neq(FxAccountsMgmtService, null);
});
@ -105,6 +109,9 @@ add_test(function test_invalidEmailCase_signIn() {
// Point the FxAccountsClient's hawk rest request client to the mock server
Services.prefs.setCharPref("identity.fxaccounts.auth.uri", server.baseURI);
// FxA device registration throws from this context
Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
// Receive a mozFxAccountsChromeEvent message
function onMessage(subject, topic, data) {
let message = subject.wrappedJSObject;
@ -164,6 +171,9 @@ add_test(function test_invalidEmailCase_signIn() {
add_test(function testHandleGetAssertionError_defaultCase() {
do_test_pending();
// FxA device registration throws from this context
Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
FxAccountsManager.getAssertion(null).then(
success => {
// getAssertion should throw with invalid audience

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="efd70ba6a54849dcef696abf1652cf74daa07899"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -35,7 +35,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="828d68eb7bdc58d4ca4c1401809a97bc7b124212"/>
<!-- Stock Android things -->
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="0f86914b89cf8a069533e66b218533a17bad6b43"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="6b1fb5b730b1299f99f9194c1fcf088579cc7977"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="efd70ba6a54849dcef696abf1652cf74daa07899"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -35,7 +35,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="828d68eb7bdc58d4ca4c1401809a97bc7b124212"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="efd70ba6a54849dcef696abf1652cf74daa07899"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -35,7 +35,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="828d68eb7bdc58d4ca4c1401809a97bc7b124212"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="efd70ba6a54849dcef696abf1652cf74daa07899"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="efd70ba6a54849dcef696abf1652cf74daa07899"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -31,7 +31,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="828d68eb7bdc58d4ca4c1401809a97bc7b124212"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<!-- Stock Android things -->

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="efd70ba6a54849dcef696abf1652cf74daa07899"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -34,7 +34,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="828d68eb7bdc58d4ca4c1401809a97bc7b124212"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="efd70ba6a54849dcef696abf1652cf74daa07899"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -34,7 +34,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="828d68eb7bdc58d4ca4c1401809a97bc7b124212"/>
<!-- Stock Android things -->
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="efd70ba6a54849dcef696abf1652cf74daa07899"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="efd70ba6a54849dcef696abf1652cf74daa07899"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -35,7 +35,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="828d68eb7bdc58d4ca4c1401809a97bc7b124212"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "43852628a9d506c65525cceb5789b257cc939fe8",
"git_revision": "efd70ba6a54849dcef696abf1652cf74daa07899",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "c1f3c74a949f3d10f802aae79a705d440b08b091",
"revision": "687b7b7814ff8a089fda4c4d6be564305a0a2b69",
"repo_path": "integration/gaia-central"
}

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="efd70ba6a54849dcef696abf1652cf74daa07899"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -35,7 +35,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="828d68eb7bdc58d4ca4c1401809a97bc7b124212"/>
<!-- Stock Android things -->
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
<project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="efd70ba6a54849dcef696abf1652cf74daa07899"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -32,7 +32,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="fake-qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="939b377d55a2f081d94029a30a75d05e5a20daf3"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="828d68eb7bdc58d4ca4c1401809a97bc7b124212"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<!-- Stock Android things -->

View File

@ -21,7 +21,7 @@
<!--
B2G repositories for all targets
-->
<project name="gaia" path="gaia" remote="mozillaorg" revision="43852628a9d506c65525cceb5789b257cc939fe8"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="efd70ba6a54849dcef696abf1652cf74daa07899"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="35dccb3127db8f39f20b985ad312d2cd44780669"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="99c333dab00ed79baff9e1cf76b320aee8e1c123"/>
<project name="platform_hardware_libhardware_moz" path="hardware/libhardware_moz" remote="b2g" revision="fdf3a143dc777e5f9d33a88373af7ea161d3b440"/>
@ -35,7 +35,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
<project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="833b5d767b5e2f683b3c9525e38c58402e394f3e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="828d68eb7bdc58d4ca4c1401809a97bc7b124212"/>
<!-- Stock Android things -->
<project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
<project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="337e0ef5e40f02a1ae59b90db0548976c70a7226"/>

View File

@ -260,15 +260,12 @@
var event = new CustomEvent("AboutNetErrorSetAutomatic",
{bubbles:true, detail:evt.target.checked});
document.dispatchEvent(event);
if (evt.target.checked && reportBtn.style.display != "none") {
if (evt.target.checked) {
sendErrorReport();
}
}, false);
var reportBtn = document.getElementById('reportCertificateError');
var retryBtn = document.getElementById('reportCertificateErrorRetry');
reportBtn.addEventListener('click', sendErrorReport, false);
retryBtn.addEventListener('click', sendErrorReport, false);
}
}
@ -532,7 +529,6 @@
<label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic2;</label>
<span id="reportingState">
<button id="reportCertificateError">&errorReporting.report;</button>
<button id="reportCertificateErrorRetry">&errorReporting.tryAgain;</button>
<span id="reportSendingMessage">&errorReporting.sending;</span>
<span id="reportSentMessage">&errorReporting.sent;</span>

View File

@ -88,6 +88,32 @@
toggleVisibility('advancedPanel');
}
var checkbox = document.getElementById("automaticallyReportInFuture");
checkbox.addEventListener("change", function ({target: {checked}}) {
document.dispatchEvent(new CustomEvent("AboutCertErrorSetAutomatic", {
detail: checked,
bubbles: true
}));
});
var retryBtn = document.getElementById("reportCertificateErrorRetry");
retryBtn.addEventListener("click", function () {
document.dispatchEvent(new CustomEvent("AboutCertErrorSendReport", {
bubbles: true
}));
});
addEventListener("AboutCertErrorOptions", function (event) {
var options = JSON.parse(event.detail);
if (options && options.enabled) {
// Display error reporting UI
document.getElementById("certificateErrorReporting").style.display = "block";
// set the checkbox
checkbox.checked = !!options.automatic;
}
}, true, true);
// Disallow overrides if this is a Strict-Transport-Security
// host and the cert is bad (STS Spec section 7.3) or if the
// certerror is in a frame (bug 633691).
@ -254,11 +280,26 @@
<div id="buttonSpacer"></div>
<button id="advancedButton" autocomplete="off" onclick="toggleVisibility('advancedPanel');" autofocus="true">&certerror.advanced.label;</button>
</div>
<!-- Advanced panel, which is hidden by default -->
<div id="advancedPanel" style="visibility: hidden;">
<p id="technicalContentText"/>
<button id="exceptionDialogButton">&certerror.addException.label;</button>
</div>
</div>
<!-- UI for option to report certificate errors to Mozilla. -->
<div id="certificateErrorReporting">
<p>
<input type="checkbox" id="automaticallyReportInFuture" />
<label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic;</label>
<span id="reportingState">
<button id="reportCertificateErrorRetry">&errorReporting.tryAgain;</button>
<span id="reportSendingMessage">&errorReporting.sending;</span>
<span id="reportSentMessage">&errorReporting.sent;</span>
</span>
</p>
</div>
<!-- Advanced panel, which is hidden by default -->
<div id="advancedPanel" style="visibility: hidden;">
<p id="technicalContentText"/>
<button id="exceptionDialogButton">&certerror.addException.label;</button>
</div>
</div>

View File

@ -2673,7 +2673,7 @@ var BrowserOnClick = {
}
break;
case "Browser:SendSSLErrorReport":
this.onSSLErrorReport(msg.target, msg.data.elementId,
this.onSSLErrorReport(msg.target,
msg.data.documentURI,
msg.data.location,
msg.data.securityInfo);
@ -2704,7 +2704,7 @@ var BrowserOnClick = {
}
},
onSSLErrorReport: function(browser, elementId, documentURI, location, securityInfo) {
onSSLErrorReport: function(browser, documentURI, location, securityInfo) {
function showReportStatus(reportStatus) {
gBrowser.selectedBrowser
.messageManager

View File

@ -207,6 +207,143 @@ const TLS_ERROR_REPORT_TELEMETRY_EXPANDED = 1;
const TLS_ERROR_REPORT_TELEMETRY_SUCCESS = 6;
const TLS_ERROR_REPORT_TELEMETRY_FAILURE = 7;
var AboutCertErrorListener = {
init(chromeGlobal) {
addMessageListener("AboutCertErrorDetails", this);
addMessageListener("Browser:SSLErrorReportStatus", this);
chromeGlobal.addEventListener("AboutCertErrorLoad", this, false, true);
chromeGlobal.addEventListener("AboutCertErrorSetAutomatic", this, false, true);
chromeGlobal.addEventListener("AboutCertErrorSendReport", this, false, true);
},
get isAboutCertError() {
return content.document.documentURI.startsWith("about:certerror");
},
handleEvent(event) {
if (!this.isAboutCertError) {
return;
}
switch (event.type) {
case "AboutCertErrorLoad":
this.onLoad(event);
break;
case "AboutCertErrorSetAutomatic":
this.onSetAutomatic(event);
break;
case "AboutCertErrorSendReport":
this.onSendReport();
break;
}
},
receiveMessage(msg) {
if (!this.isAboutCertError) {
return;
}
switch (msg.name) {
case "AboutCertErrorDetails":
this.onDetails(msg);
break;
case "Browser:SSLErrorReportStatus":
this.onReportStatus(msg);
break;
}
},
onLoad(event) {
let originalTarget = event.originalTarget;
let ownerDoc = originalTarget.ownerDocument;
ClickEventHandler.onAboutCertError(originalTarget, ownerDoc);
let automatic = Services.prefs.getBoolPref("security.ssl.errorReporting.automatic");
content.dispatchEvent(new content.CustomEvent("AboutCertErrorOptions", {
detail: JSON.stringify({
enabled: Services.prefs.getBoolPref("security.ssl.errorReporting.enabled"),
automatic,
})
}));
if (automatic) {
this.onSendReport();
}
},
onDetails(msg) {
let div = content.document.getElementById("certificateErrorText");
div.textContent = msg.data.info;
},
onSetAutomatic(event) {
if (event.detail) {
this.onSendReport();
}
sendAsyncMessage("Browser:SetSSLErrorReportAuto", {
automatic: event.detail
});
},
onSendReport() {
let doc = content.document;
let location = doc.location.href;
let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
.getService(Ci.nsISerializationHelper);
let serializable = docShell.failedChannel.securityInfo
.QueryInterface(Ci.nsITransportSecurityInfo)
.QueryInterface(Ci.nsISerializable);
let serializedSecurityInfo = serhelper.serializeToString(serializable);
sendAsyncMessage("Browser:SendSSLErrorReport", {
documentURI: doc.documentURI,
location: {hostname: doc.location.hostname, port: doc.location.port},
securityInfo: serializedSecurityInfo
});
},
onReportStatus(msg) {
let doc = content.document;
if (doc.documentURI != msg.data.documentURI) {
return;
}
let reportSendingMsg = doc.getElementById("reportSendingMessage");
let reportSentMsg = doc.getElementById("reportSentMessage");
let retryBtn = doc.getElementById("reportCertificateErrorRetry");
switch (msg.data.reportStatus) {
case "activity":
// Hide the button that was just clicked
retryBtn.style.removeProperty("display");
reportSentMsg.style.removeProperty("display");
reportSendingMsg.style.display = "block";
break;
case "error":
// show the retry button
retryBtn.style.display = "block";
reportSendingMsg.style.removeProperty("display");
sendAsyncMessage("Browser:SSLErrorReportTelemetry",
{reportStatus: TLS_ERROR_REPORT_TELEMETRY_FAILURE});
break;
case "complete":
// Show a success indicator
reportSentMsg.style.display = "block";
reportSendingMsg.style.removeProperty("display");
sendAsyncMessage("Browser:SSLErrorReportTelemetry",
{reportStatus: TLS_ERROR_REPORT_TELEMETRY_SUCCESS});
break;
}
}
};
AboutCertErrorListener.init(this);
var AboutNetErrorListener = {
init: function(chromeGlobal) {
chromeGlobal.addEventListener('AboutNetErrorLoad', this, false, true);
@ -283,7 +420,6 @@ var AboutNetErrorListener = {
let reportSendingMsg = contentDoc.getElementById("reportSendingMessage");
let reportSentMsg = contentDoc.getElementById("reportSentMessage");
let reportBtn = contentDoc.getElementById("reportCertificateError");
let retryBtn = contentDoc.getElementById("reportCertificateErrorRetry");
addMessageListener("Browser:SSLErrorReportStatus", function(message) {
@ -293,7 +429,6 @@ var AboutNetErrorListener = {
switch(message.data.reportStatus) {
case "activity":
// Hide the button that was just clicked
reportBtn.style.display = "none";
retryBtn.style.display = "none";
reportSentMsg.style.display = "none";
reportSendingMsg.style.removeProperty("display");
@ -319,20 +454,22 @@ var AboutNetErrorListener = {
let location = contentDoc.location.href;
let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
.getService(Ci.nsISerializationHelper);
.getService(Ci.nsISerializationHelper);
let serializable = docShell.failedChannel.securityInfo
.QueryInterface(Ci.nsITransportSecurityInfo)
.QueryInterface(Ci.nsISerializable);
let serializable = docShell.failedChannel.securityInfo
.QueryInterface(Ci.nsITransportSecurityInfo)
.QueryInterface(Ci.nsISerializable);
let serializedSecurityInfo = serhelper.serializeToString(serializable);
sendAsyncMessage("Browser:SendSSLErrorReport", {
elementId: evt.target.id,
documentURI: contentDoc.documentURI,
location: {hostname: contentDoc.location.hostname, port: contentDoc.location.port},
securityInfo: serializedSecurityInfo
});
documentURI: contentDoc.documentURI,
location: {
hostname: contentDoc.location.hostname,
port: contentDoc.location.port
},
securityInfo: serializedSecurityInfo
});
},
onOverride: function(evt) {
@ -553,17 +690,6 @@ addEventListener("DOMServiceWorkerFocusClient", function(event) {
sendAsyncMessage("DOMServiceWorkerFocusClient", {});
}, false);
addEventListener("AboutCertErrorLoad", function(event) {
let originalTarget = event.originalTarget;
let ownerDoc = originalTarget.ownerDocument;
ClickEventHandler.onAboutCertError(originalTarget, ownerDoc);
}, false, true);
addMessageListener("AboutCertErrorDetails", function(message) {
let div = content.document.getElementById("certificateErrorText");
div.textContent = message.data.info;
});
ContentWebRTC.init();
addMessageListener("rtcpeer:Allow", ContentWebRTC);
addMessageListener("rtcpeer:Deny", ContentWebRTC);

View File

@ -4111,13 +4111,29 @@
browser.messageManager.sendAsyncMessage("Browser:AppTab", { isAppTab: tab.pinned })
break;
}
case "Findbar:Keypress":
if (!gFindBarInitialized) {
// If the find bar for this tab is not yet alive, change that,
// and make sure we return the result:
return gFindBar.receiveMessage(aMessage);
case "Findbar:Keypress": {
let tab = this.getTabForBrowser(browser);
// If the find bar for this tab is not yet alive, only initialize
// it if there's a possibility FindAsYouType will be used.
// There's no point in doing it for most random keypresses.
if (!this.isFindBarInitialized(tab) &&
aMessage.data.shouldFastFind) {
let shouldFastFind = this._findAsYouType;
if (!shouldFastFind) {
// Please keep in sync with toolkit/content/widgets/findbar.xml
const FAYT_LINKS_KEY = "'";
const FAYT_TEXT_KEY = "/";
let charCode = aMessage.data.fakeEvent.charCode;
let key = charCode ? String.fromCharCode(charCode) : null;
shouldFastFind = key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY;
}
if (shouldFastFind) {
// Make sure we return the result.
return this.getFindBar(tab).receiveMessage(aMessage);
}
}
break;
}
}
]]></body>
@ -4128,22 +4144,30 @@
<parameter name="aTopic"/>
<parameter name="aData"/>
<body><![CDATA[
if (aTopic == "live-resize-start") {
let browser = this.mCurrentTab.linkedBrowser;
let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
if (fl && fl.tabParent && !this.mActiveResizeDisplayportSuppression) {
fl.tabParent.suppressDisplayport(true);
this.mActiveResizeDisplayportSuppression = browser;
}
} else if (aTopic == "live-resize-end") {
let browser = this.mActiveResizeDisplayportSuppression;
if (browser) {
let browser;
switch(aTopic) {
case "live-resize-start":
browser = this.mCurrentTab.linkedBrowser;
let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
if (fl && fl.tabParent) {
fl.tabParent.suppressDisplayport(false);
this.mActiveResizeDisplayportSuppression = null;
if (fl && fl.tabParent && !this.mActiveResizeDisplayportSuppression) {
fl.tabParent.suppressDisplayport(true);
this.mActiveResizeDisplayportSuppression = browser;
}
}
break;
case "live-resize-end":
browser = this.mActiveResizeDisplayportSuppression;
if (browser) {
let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
if (fl && fl.tabParent) {
fl.tabParent.suppressDisplayport(false);
this.mActiveResizeDisplayportSuppression = null;
}
}
break;
case "nsPref:changed":
// This is the only pref observed.
this._findAsYouType = Services.prefs.getBoolPref("accessibility.typeaheadfind");
break;
}
]]></body>
</method>
@ -4219,6 +4243,11 @@
}
messageManager.addMessageListener("DOMWebNotificationClicked", this);
messageManager.addMessageListener("DOMServiceWorkerFocusClient", this);
// To correctly handle keypresses for potential FindAsYouType, while
// the tab's find bar is not yet initialized.
this._findAsYouType = Services.prefs.getBoolPref("accessibility.typeaheadfind");
Services.prefs.addObserver("accessibility.typeaheadfind", this, false);
messageManager.addMessageListener("Findbar:Keypress", this);
]]>
</constructor>
@ -4280,6 +4309,8 @@
this._switcher.destroy();
}
}
Services.prefs.removeObserver("accessibility.typeaheadfind", this);
]]>
</destructor>

View File

@ -16,7 +16,6 @@ support-files =
browser_fxa_oauth_with_keys.html
browser_fxa_web_channel.html
browser_registerProtocolHandler_notification.html
browser_ssl_error_reports_content.js
browser_star_hsts.sjs
browser_tab_dragdrop2_frame1.xul
browser_web_channel.html
@ -67,6 +66,8 @@ support-files =
file_favicon_change_not_in_document.html
file_fullscreen-window-open.html
get_user_media.html
get_user_media_content_script.js
get_user_media_helpers.js
head.js
healthreport_pingData.js
healthreport_testRemoteCommands.html
@ -77,7 +78,7 @@ support-files =
page_style_sample.html
parsingTestHelpers.jsm
pinning_headers.sjs
pinning_reports.sjs
ssl_error_reports.sjs
popup_blocker.html
print_postdata.sjs
redirect_bug623155.sjs
@ -294,7 +295,6 @@ skip-if = buildapp == 'mulet' || (os == "linux" && debug) # linux: bug 976544
[browser_devices_get_user_media_about_urls.js]
skip-if = e10s # Bug 1071623
[browser_devices_get_user_media_in_frame.js]
skip-if = e10s # Bug 1071623
[browser_discovery.js]
[browser_double_close_tab.js]
[browser_documentnavigation.js]

View File

@ -4,211 +4,11 @@
requestLongerTimeout(2);
const PREF_PERMISSION_FAKE = "media.navigator.permission.fake";
let frameScript = function() {
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
"@mozilla.org/mediaManagerService;1",
"nsIMediaManagerService");
const kObservedTopics = [
"getUserMedia:response:allow",
"getUserMedia:revoke",
"getUserMedia:response:deny",
"getUserMedia:request",
"recording-device-events",
"recording-window-ended"
];
var gObservedTopics = {};
function observer(aSubject, aTopic, aData) {
if (!(aTopic in gObservedTopics))
gObservedTopics[aTopic] = 1;
else
++gObservedTopics[aTopic];
}
kObservedTopics.forEach(topic => {
Services.obs.addObserver(observer, topic, false);
});
addMessageListener("Test:ExpectObserverCalled", ({data}) => {
sendAsyncMessage("Test:ExpectObserverCalled:Reply",
{count: gObservedTopics[data]});
if (data in gObservedTopics)
--gObservedTopics[data];
});
addMessageListener("Test:TodoObserverNotCalled", ({data}) => {
sendAsyncMessage("Test:TodoObserverNotCalled:Reply",
{count: gObservedTopics[data]});
if (gObservedTopics[data] == 1)
gObservedTopics[data] = 0;
});
addMessageListener("Test:ExpectNoObserverCalled", data => {
sendAsyncMessage("Test:ExpectNoObserverCalled:Reply", gObservedTopics);
gObservedTopics = {};
});
function _getMediaCaptureState() {
let hasVideo = {};
let hasAudio = {};
MediaManagerService.mediaCaptureWindowState(content, hasVideo, hasAudio);
if (hasVideo.value && hasAudio.value)
return "CameraAndMicrophone";
if (hasVideo.value)
return "Camera";
if (hasAudio.value)
return "Microphone";
return "none";
}
addMessageListener("Test:GetMediaCaptureState", data => {
sendAsyncMessage("Test:MediaCaptureState", _getMediaCaptureState());
});
addMessageListener("Test:WaitForObserverCall", ({data}) => {
let topic = data;
Services.obs.addObserver(function observer() {
sendAsyncMessage("Test:ObserverCalled", topic);
Services.obs.removeObserver(observer, topic);
if (kObservedTopics.indexOf(topic) != -1) {
if (!(topic in gObservedTopics))
gObservedTopics[topic] = -1;
else
--gObservedTopics[topic];
}
}, topic, false);
});
}; // end of framescript
function _mm() {
return gBrowser.selectedBrowser.messageManager;
}
function promiseObserverCalled(aTopic) {
return new Promise(resolve => {
let mm = _mm();
mm.addMessageListener("Test:ObserverCalled", function listener({data}) {
if (data == aTopic) {
ok(true, "got " + aTopic + " notification");
mm.removeMessageListener("Test:ObserverCalled", listener);
resolve();
}
});
mm.sendAsyncMessage("Test:WaitForObserverCall", aTopic);
});
}
function expectObserverCalled(aTopic) {
return new Promise(resolve => {
let mm = _mm();
mm.addMessageListener("Test:ExpectObserverCalled:Reply",
function listener({data}) {
is(data.count, 1, "expected notification " + aTopic);
mm.removeMessageListener("Test:ExpectObserverCalled:Reply", listener);
resolve();
});
mm.sendAsyncMessage("Test:ExpectObserverCalled", aTopic);
});
}
function expectNoObserverCalled() {
return new Promise(resolve => {
let mm = _mm();
mm.addMessageListener("Test:ExpectNoObserverCalled:Reply",
function listener({data}) {
mm.removeMessageListener("Test:ExpectNoObserverCalled:Reply", listener);
for (let topic in data) {
if (data[topic])
is(data[topic], 0, topic + " notification unexpected");
}
resolve();
});
mm.sendAsyncMessage("Test:ExpectNoObserverCalled");
});
}
function promiseTodoObserverNotCalled(aTopic) {
return new Promise(resolve => {
let mm = _mm();
mm.addMessageListener("Test:TodoObserverNotCalled:Reply",
function listener({data}) {
mm.removeMessageListener("Test:TodoObserverNotCalled:Reply", listener);
resolve(data.count);
});
mm.sendAsyncMessage("Test:TodoObserverNotCalled", aTopic);
});
}
function promiseMessage(aMessage, aAction) {
let deferred = Promise.defer();
content.addEventListener("message", function messageListener(event) {
content.removeEventListener("message", messageListener);
is(event.data, aMessage, "received " + aMessage);
if (event.data == aMessage)
deferred.resolve();
else
deferred.reject();
});
if (aAction)
aAction();
return deferred.promise;
}
function promisePopupNotificationShown(aName, aAction) {
let deferred = Promise.defer();
PopupNotifications.panel.addEventListener("popupshown", function popupNotifShown() {
PopupNotifications.panel.removeEventListener("popupshown", popupNotifShown);
ok(!!PopupNotifications.getNotification(aName), aName + " notification shown");
ok(PopupNotifications.isPanelOpen, "notification panel open");
ok(!!PopupNotifications.panel.firstChild, "notification panel populated");
deferred.resolve();
});
if (aAction)
aAction();
return deferred.promise;
}
function promisePopupNotification(aName) {
let deferred = Promise.defer();
waitForCondition(() => PopupNotifications.getNotification(aName),
() => {
ok(!!PopupNotifications.getNotification(aName),
aName + " notification appeared");
deferred.resolve();
}, "timeout waiting for popup notification " + aName);
return deferred.promise;
}
function promiseNoPopupNotification(aName) {
let deferred = Promise.defer();
waitForCondition(() => !PopupNotifications.getNotification(aName),
() => {
ok(!PopupNotifications.getNotification(aName),
aName + " notification removed");
deferred.resolve();
}, "timeout waiting for popup notification " + aName + " to disappear");
return deferred.promise;
}
const CONTENT_SCRIPT_HELPER = getRootDirectory(gTestPath) + "get_user_media_content_script.js";
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript(getRootDirectory(gTestPath) + "get_user_media_helpers.js",
this);
function enableDevice(aType, aEnabled) {
let menulist = document.getElementById("webRTC-select" + aType + "-menulist");
@ -216,105 +16,10 @@ function enableDevice(aType, aEnabled) {
menulist.value = aEnabled ? menupopup.firstChild.getAttribute("value") : "-1";
}
const kActionAlways = 1;
const kActionDeny = 2;
const kActionNever = 3;
function activateSecondaryAction(aAction) {
let notification = PopupNotifications.panel.firstChild;
notification.button.focus();
let popup = notification.menupopup;
popup.addEventListener("popupshown", function () {
popup.removeEventListener("popupshown", arguments.callee, false);
// Press 'down' as many time as needed to select the requested action.
while (aAction--)
EventUtils.synthesizeKey("VK_DOWN", {});
// Activate
EventUtils.synthesizeKey("VK_RETURN", {});
}, false);
// One down event to open the popup
EventUtils.synthesizeKey("VK_DOWN",
{ altKey: !navigator.platform.includes("Mac") });
}
registerCleanupFunction(function() {
gBrowser.removeCurrentTab();
});
function getMediaCaptureState() {
return new Promise(resolve => {
let mm = _mm();
mm.addMessageListener("Test:MediaCaptureState", ({data}) => {
resolve(data);
});
mm.sendAsyncMessage("Test:GetMediaCaptureState");
});
}
function promiseRequestDevice(aRequestAudio, aRequestVideo) {
info("requesting devices");
return ContentTask.spawn(gBrowser.selectedBrowser,
{aRequestAudio, aRequestVideo},
function*(args) {
content.wrappedJSObject.requestDevice(args.aRequestAudio,
args.aRequestVideo);
});
}
function* closeStream(aAlreadyClosed) {
yield expectNoObserverCalled();
let promise;
if (!aAlreadyClosed)
promise = promiseObserverCalled("recording-device-events");
info("closing the stream");
yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
content.wrappedJSObject.closeStream();
});
if (!aAlreadyClosed)
yield promise;
yield promiseNoPopupNotification("webRTC-sharingDevices");
if (!aAlreadyClosed)
yield expectObserverCalled("recording-window-ended");
yield* assertWebRTCIndicatorStatus(null);
}
function checkDeviceSelectors(aAudio, aVideo) {
let micSelector = document.getElementById("webRTC-selectMicrophone");
if (aAudio)
ok(!micSelector.hidden, "microphone selector visible");
else
ok(micSelector.hidden, "microphone selector hidden");
let cameraSelector = document.getElementById("webRTC-selectCamera");
if (aVideo)
ok(!cameraSelector.hidden, "camera selector visible");
else
ok(cameraSelector.hidden, "camera selector hidden");
}
function* checkSharingUI(aExpected) {
yield promisePopupNotification("webRTC-sharingDevices");
yield* assertWebRTCIndicatorStatus(aExpected);
}
function* checkNotSharing() {
is((yield getMediaCaptureState()), "none", "expected nothing to be shared");
ok(!PopupNotifications.getNotification("webRTC-sharingDevices"),
"no webRTC-sharingDevices popup notification");
yield* assertWebRTCIndicatorStatus(null);
}
const permissionError = "error: SecurityError: The operation is insecure.";
var gTests = [
@ -985,7 +690,7 @@ function test() {
gBrowser.selectedTab = tab;
let browser = tab.linkedBrowser;
browser.messageManager.loadFrameScript("data:,(" + frameScript.toString() + ")();", true);
browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
browser.addEventListener("load", function onload() {
browser.removeEventListener("load", onload, true);
@ -1015,10 +720,3 @@ function test() {
"https://example.com/");
content.location = rootDir + "get_user_media.html";
}
function wait(time) {
let deferred = Promise.defer();
setTimeout(deferred.resolve, time);
return deferred.promise;
}

View File

@ -2,223 +2,20 @@
* 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/. */
const kObservedTopics = [
"getUserMedia:response:allow",
"getUserMedia:revoke",
"getUserMedia:response:deny",
"getUserMedia:request",
"recording-device-events",
"recording-window-ended"
];
const PREF_PERMISSION_FAKE = "media.navigator.permission.fake";
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
"@mozilla.org/mediaManagerService;1",
"nsIMediaManagerService");
var gObservedTopics = {};
function observer(aSubject, aTopic, aData) {
if (!(aTopic in gObservedTopics))
gObservedTopics[aTopic] = 1;
else
++gObservedTopics[aTopic];
}
function promiseObserverCalled(aTopic, aAction) {
let deferred = Promise.defer();
Services.obs.addObserver(function observer() {
ok(true, "got " + aTopic + " notification");
Services.obs.removeObserver(observer, aTopic);
if (kObservedTopics.indexOf(aTopic) != -1) {
if (!(aTopic in gObservedTopics))
gObservedTopics[aTopic] = -1;
else
--gObservedTopics[aTopic];
}
deferred.resolve();
}, aTopic, false);
if (aAction)
aAction();
return deferred.promise;
}
function expectObserverCalled(aTopic) {
is(gObservedTopics[aTopic], 1, "expected notification " + aTopic);
if (aTopic in gObservedTopics)
--gObservedTopics[aTopic];
}
function expectNoObserverCalled() {
for (let topic in gObservedTopics) {
if (gObservedTopics[topic])
is(gObservedTopics[topic], 0, topic + " notification unexpected");
}
gObservedTopics = {};
}
function promiseMessage(aMessage, aAction) {
let deferred = Promise.defer();
content.addEventListener("message", function messageListener(event) {
content.removeEventListener("message", messageListener);
is(event.data, aMessage, "received " + aMessage);
if (event.data == aMessage)
deferred.resolve();
else
deferred.reject();
});
if (aAction)
aAction();
return deferred.promise;
}
function promisePopupNotificationShown(aName, aAction) {
let deferred = Promise.defer();
PopupNotifications.panel.addEventListener("popupshown", function popupNotifShown() {
PopupNotifications.panel.removeEventListener("popupshown", popupNotifShown);
ok(!!PopupNotifications.getNotification(aName), aName + " notification shown");
ok(PopupNotifications.isPanelOpen, "notification panel open");
ok(!!PopupNotifications.panel.firstChild, "notification panel populated");
deferred.resolve();
});
if (aAction)
aAction();
return deferred.promise;
}
function promisePopupNotification(aName) {
let deferred = Promise.defer();
waitForCondition(() => PopupNotifications.getNotification(aName),
() => {
ok(!!PopupNotifications.getNotification(aName),
aName + " notification appeared");
deferred.resolve();
}, "timeout waiting for popup notification " + aName);
return deferred.promise;
}
function promiseNoPopupNotification(aName) {
let deferred = Promise.defer();
waitForCondition(() => !PopupNotifications.getNotification(aName),
() => {
ok(!PopupNotifications.getNotification(aName),
aName + " notification removed");
deferred.resolve();
}, "timeout waiting for popup notification " + aName + " to disappear");
return deferred.promise;
}
const kActionAlways = 1;
const kActionDeny = 2;
const kActionNever = 3;
function activateSecondaryAction(aAction) {
let notification = PopupNotifications.panel.firstChild;
notification.button.focus();
let popup = notification.menupopup;
popup.addEventListener("popupshown", function () {
popup.removeEventListener("popupshown", arguments.callee, false);
// Press 'down' as many time as needed to select the requested action.
while (aAction--)
EventUtils.synthesizeKey("VK_DOWN", {});
// Activate
EventUtils.synthesizeKey("VK_RETURN", {});
}, false);
// One down event to open the popup
EventUtils.synthesizeKey("VK_DOWN",
{ altKey: !navigator.platform.includes("Mac") });
}
const CONTENT_SCRIPT_HELPER = getRootDirectory(gTestPath) + "get_user_media_content_script.js";
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript(getRootDirectory(gTestPath) + "get_user_media_helpers.js",
this);
registerCleanupFunction(function() {
gBrowser.removeCurrentTab();
kObservedTopics.forEach(topic => {
Services.obs.removeObserver(observer, topic);
});
Services.prefs.clearUserPref(PREF_PERMISSION_FAKE);
});
function getMediaCaptureState() {
let hasVideo = {};
let hasAudio = {};
MediaManagerService.mediaCaptureWindowState(content, hasVideo, hasAudio);
if (hasVideo.value && hasAudio.value)
return "CameraAndMicrophone";
if (hasVideo.value)
return "Camera";
if (hasAudio.value)
return "Microphone";
return "none";
}
function* closeStream(aGlobal, aAlreadyClosed) {
expectNoObserverCalled();
info("closing the stream");
aGlobal.closeStream();
if (!aAlreadyClosed)
yield promiseObserverCalled("recording-device-events");
yield promiseNoPopupNotification("webRTC-sharingDevices");
if (!aAlreadyClosed)
expectObserverCalled("recording-window-ended");
yield* assertWebRTCIndicatorStatus(null);
}
function checkDeviceSelectors(aAudio, aVideo) {
let micSelector = document.getElementById("webRTC-selectMicrophone");
if (aAudio)
ok(!micSelector.hidden, "microphone selector visible");
else
ok(micSelector.hidden, "microphone selector hidden");
let cameraSelector = document.getElementById("webRTC-selectCamera");
if (aVideo)
ok(!cameraSelector.hidden, "camera selector visible");
else
ok(cameraSelector.hidden, "camera selector hidden");
}
function* checkSharingUI(aExpected) {
yield promisePopupNotification("webRTC-sharingDevices");
yield* assertWebRTCIndicatorStatus(aExpected);
}
function* checkNotSharing() {
is(getMediaCaptureState(), "none", "expected nothing to be shared");
ok(!PopupNotifications.getNotification("webRTC-sharingDevices"),
"no webRTC-sharingDevices popup notification");
yield* assertWebRTCIndicatorStatus(null);
}
function getFrameGlobal(aFrameId) {
return content.wrappedJSObject.document.getElementById(aFrameId).contentWindow;
function promiseReloadFrame(aFrameId) {
return ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(aFrameId) {
content.wrappedJSObject.document.getElementById(aFrameId).contentWindow.location.reload();
});
}
var gTests = [
@ -226,12 +23,10 @@ var gTests = [
{
desc: "getUserMedia audio+video",
run: function checkAudioVideo() {
let global = getFrameGlobal("frame1");
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting devices");
global.requestDevice(true, true);
});
expectObserverCalled("getUserMedia:request");
let promise = promisePopupNotificationShown("webRTC-shareDevices");
yield promiseRequestDevice(true, true, "frame1");
yield promise;
yield expectObserverCalled("getUserMedia:request");
is(PopupNotifications.getNotification("webRTC-shareDevices").anchorID,
"webRTC-shareDevices-notification-icon", "anchored to device icon");
@ -243,35 +38,33 @@ var gTests = [
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
expectObserverCalled("getUserMedia:response:allow");
expectObserverCalled("recording-device-events");
is(getMediaCaptureState(), "CameraAndMicrophone",
yield expectObserverCalled("getUserMedia:response:allow");
yield expectObserverCalled("recording-device-events");
is((yield getMediaCaptureState()), "CameraAndMicrophone",
"expected camera and microphone to be shared");
yield indicator;
yield checkSharingUI({audio: true, video: true});
yield closeStream(global);
yield closeStream(false, "frame1");
}
},
{
desc: "getUserMedia audio+video: stop sharing",
run: function checkStopSharing() {
let global = getFrameGlobal("frame1");
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting devices");
global.requestDevice(true, true);
});
expectObserverCalled("getUserMedia:request");
let promise = promisePopupNotificationShown("webRTC-shareDevices");
yield promiseRequestDevice(true, true, "frame1");
yield promise;
yield expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, true);
let indicator = promiseIndicatorWindow();
yield promiseMessage("ok", () => {
activateSecondaryAction(kActionAlways);
});
expectObserverCalled("getUserMedia:response:allow");
expectObserverCalled("recording-device-events");
is(getMediaCaptureState(), "CameraAndMicrophone",
yield expectObserverCalled("getUserMedia:response:allow");
yield expectObserverCalled("recording-device-events");
is((yield getMediaCaptureState()), "CameraAndMicrophone",
"expected camera and microphone to be shared");
yield indicator;
@ -288,17 +81,16 @@ var gTests = [
activateSecondaryAction(kActionDeny);
yield promiseObserverCalled("recording-device-events");
expectObserverCalled("getUserMedia:revoke");
yield expectObserverCalled("getUserMedia:revoke");
yield promiseNoPopupNotification("webRTC-sharingDevices");
expectObserverCalled("recording-window-ended");
yield expectObserverCalled("recording-window-ended");
if (gObservedTopics["recording-device-events"] == 1) {
if ((yield promiseTodoObserverNotCalled("recording-device-events")) == 1) {
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
gObservedTopics["recording-device-events"] = 0;
}
expectNoObserverCalled();
yield expectNoObserverCalled();
yield checkNotSharing();
// The persistent permissions for the frame should have been removed.
@ -308,44 +100,42 @@ var gTests = [
"camera not persistently allowed");
// the stream is already closed, but this will do some cleanup anyway
yield closeStream(global, true);
yield closeStream(true, "frame1");
}
},
{
desc: "getUserMedia audio+video: reloading the frame removes all sharing UI",
run: function checkReloading() {
let global = getFrameGlobal("frame1");
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting devices");
global.requestDevice(true, true);
});
expectObserverCalled("getUserMedia:request");
let promise = promisePopupNotificationShown("webRTC-shareDevices");
yield promiseRequestDevice(true, true, "frame1");
yield promise;
yield expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, true);
let indicator = promiseIndicatorWindow();
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
expectObserverCalled("getUserMedia:response:allow");
expectObserverCalled("recording-device-events");
is(getMediaCaptureState(), "CameraAndMicrophone",
yield expectObserverCalled("getUserMedia:response:allow");
yield expectObserverCalled("recording-device-events");
is((yield getMediaCaptureState()), "CameraAndMicrophone",
"expected camera and microphone to be shared");
yield indicator;
yield checkSharingUI({video: true, audio: true});
info("reloading the frame");
yield promiseObserverCalled("recording-device-events",
() => { global.location.reload(); });
promise = promiseObserverCalled("recording-device-events");
yield promiseReloadFrame("frame1");
yield promise;
yield promiseNoPopupNotification("webRTC-sharingDevices");
if (gObservedTopics["recording-device-events"] == 1) {
if ((yield promiseTodoObserverNotCalled("recording-device-events")) == 1) {
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
gObservedTopics["recording-device-events"] = 0;
}
expectObserverCalled("recording-window-ended");
expectNoObserverCalled();
yield expectObserverCalled("recording-window-ended");
yield expectNoObserverCalled();
yield checkNotSharing();
}
},
@ -353,21 +143,19 @@ var gTests = [
{
desc: "getUserMedia audio+video: reloading the frame removes prompts",
run: function checkReloadingRemovesPrompts() {
let global = getFrameGlobal("frame1");
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting devices");
global.requestDevice(true, true);
});
expectObserverCalled("getUserMedia:request");
let promise = promisePopupNotificationShown("webRTC-shareDevices");
yield promiseRequestDevice(true, true, "frame1");
yield promise;
yield expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, true);
info("reloading the frame");
yield promiseObserverCalled("recording-window-ended",
() => { global.location.reload(); });
promise = promiseObserverCalled("recording-window-ended");
yield promiseReloadFrame("frame1");
yield promise;
yield promiseNoPopupNotification("webRTC-shareDevices");
expectNoObserverCalled();
yield expectNoObserverCalled();
yield checkNotSharing();
}
},
@ -378,60 +166,57 @@ var gTests = [
// We'll share only the mic in the first frame, then share both in the
// second frame, then reload the second frame. After each step, we'll check
// the UI is in the correct state.
let g1 = getFrameGlobal("frame1"), g2 = getFrameGlobal("frame2");
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting microphone in the first frame");
g1.requestDevice(true, false);
});
expectObserverCalled("getUserMedia:request");
let promise = promisePopupNotificationShown("webRTC-shareDevices");
yield promiseRequestDevice(true, false, "frame1");
yield promise;
yield expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, false);
let indicator = promiseIndicatorWindow();
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
expectObserverCalled("getUserMedia:response:allow");
expectObserverCalled("recording-device-events");
is(getMediaCaptureState(), "Microphone", "microphone to be shared");
yield expectObserverCalled("getUserMedia:response:allow");
yield expectObserverCalled("recording-device-events");
is((yield getMediaCaptureState()), "Microphone", "microphone to be shared");
yield indicator;
yield checkSharingUI({video: false, audio: true});
expectNoObserverCalled();
yield expectNoObserverCalled();
yield promisePopupNotificationShown("webRTC-shareDevices", () => {
info("requesting both devices in the second frame");
g2.requestDevice(true, true);
});
expectObserverCalled("getUserMedia:request");
promise = promisePopupNotificationShown("webRTC-shareDevices");
yield promiseRequestDevice(true, true, "frame2");
yield promise;
yield expectObserverCalled("getUserMedia:request");
checkDeviceSelectors(true, true);
yield promiseMessage("ok", () => {
PopupNotifications.panel.firstChild.button.click();
});
expectObserverCalled("getUserMedia:response:allow");
expectObserverCalled("recording-device-events");
is(getMediaCaptureState(), "CameraAndMicrophone",
yield expectObserverCalled("getUserMedia:response:allow");
yield expectObserverCalled("recording-device-events");
is((yield getMediaCaptureState()), "CameraAndMicrophone",
"expected camera and microphone to be shared");
yield checkSharingUI({video: true, audio: true});
expectNoObserverCalled();
yield expectNoObserverCalled();
info("reloading the second frame");
yield promiseObserverCalled("recording-device-events",
() => { g2.location.reload(); });
promise = promiseObserverCalled("recording-device-events");
yield promiseReloadFrame("frame2");
yield promise;
yield checkSharingUI({video: false, audio: true});
expectObserverCalled("recording-window-ended");
if (gObservedTopics["recording-device-events"] == 1) {
yield expectObserverCalled("recording-window-ended");
if ((yield promiseTodoObserverNotCalled("recording-device-events")) == 1) {
todo(false, "Got the 'recording-device-events' notification twice, likely because of bug 962719");
gObservedTopics["recording-device-events"] = 0;
}
expectNoObserverCalled();
yield expectNoObserverCalled();
yield closeStream(g1);
yield closeStream(false, "frame1");
yield promiseNoPopupNotification("webRTC-sharingDevices");
expectNoObserverCalled();
yield expectNoObserverCalled();
yield checkNotSharing();
}
}
@ -443,24 +228,27 @@ function test() {
let tab = gBrowser.addTab();
gBrowser.selectedTab = tab;
tab.linkedBrowser.addEventListener("load", function onload() {
tab.linkedBrowser.removeEventListener("load", onload, true);
let browser = tab.linkedBrowser;
kObservedTopics.forEach(topic => {
Services.obs.addObserver(observer, topic, false);
});
Services.prefs.setBoolPref(PREF_PERMISSION_FAKE, true);
browser.messageManager.loadFrameScript(CONTENT_SCRIPT_HELPER, true);
browser.addEventListener("load", function onload() {
browser.removeEventListener("load", onload, true);
is(PopupNotifications._currentNotifications.length, 0,
"should start the test without any prior popup notification");
Task.spawn(function () {
yield new Promise(resolve => SpecialPowers.pushPrefEnv({
"set": [[PREF_PERMISSION_FAKE, true]],
}, resolve));
for (let test of gTests) {
info(test.desc);
yield test.run();
// Cleanup before the next test
expectNoObserverCalled();
yield expectNoObserverCalled();
}
}).then(finish, ex => {
ok(false, "Unexpected Exception: " + ex);

View File

@ -1,307 +1,217 @@
"use strict";
var badChainURL = "https://badchain.include-subdomains.pinning.example.com";
var noCertURL = "https://fail-handshake.example.com";
var enabledPref = false;
var automaticPref = false;
var urlPref = "security.ssl.errorReporting.url";
var enforcement_level = 1;
var ROOT = getRootDirectory(gTestPath);
const URL_REPORTS = "https://example.com/browser/browser/base/content/test/general/ssl_error_reports.sjs?";
const URL_BAD_CHAIN = "https://badchain.include-subdomains.pinning.example.com/";
const URL_NO_CERT = "https://fail-handshake.example.com/";
const URL_BAD_CERT = "https://expired.example.com/";
const URL_BAD_STS_CERT = "https://badchain.include-subdomains.pinning.example.com:443/";
const ROOT = getRootDirectory(gTestPath);
const PREF_REPORT_ENABLED = "security.ssl.errorReporting.enabled";
const PREF_REPORT_AUTOMATIC = "security.ssl.errorReporting.automatic";
const PREF_REPORT_URL = "security.ssl.errorReporting.url";
SimpleTest.requestCompleteLog();
add_task(function* test_send_report_manual_badchain() {
yield testSendReportManual(badChainURL, "succeed");
});
Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2);
add_task(function* test_send_report_manual_nocert() {
yield testSendReportManual(noCertURL, "nocert");
});
// creates a promise of the message in an error page
function createNetworkErrorMessagePromise(aBrowser) {
let promise = new Promise(function(resolve, reject) {
let originalDocumentURI = aBrowser.contentDocument.documentURI;
let loadedListener = function() {
let doc = aBrowser.contentDocument;
if (doc && doc.getElementById("reportCertificateError")) {
let documentURI = doc.documentURI;
aBrowser.removeEventListener("DOMContentLoaded", loadedListener, true);
let matchArray = /about:neterror\?.*&d=([^&]*)/.exec(documentURI);
if (!matchArray) {
reject("no network error message found in URI");
return;
}
let errorMsg = matchArray[1];
resolve(decodeURIComponent(errorMsg));
}
};
aBrowser.addEventListener("DOMContentLoaded", loadedListener, true);
});
return promise;
function cleanup() {
Services.prefs.clearUserPref(PREF_REPORT_ENABLED);
Services.prefs.clearUserPref(PREF_REPORT_AUTOMATIC);
Services.prefs.clearUserPref(PREF_REPORT_URL);
}
// check we can set the 'automatically send' pref
add_task(function* test_set_automatic() {
setup();
let tab = gBrowser.addTab(badChainURL, {skipAnimation: true});
let browser = tab.linkedBrowser;
let mm = browser.messageManager;
mm.loadFrameScript(ROOT + "browser_ssl_error_reports_content.js", true);
gBrowser.selectedTab = tab;
// ensure we have the correct error message from about:neterror
let netError = createNetworkErrorMessagePromise(browser);
yield netError;
// ensure that setting automatic when unset works
let prefEnabled = new Promise(function(resolve, reject){
let prefUpdateListener = function() {
mm.removeMessageListener("ssler-test:AutoPrefUpdated", prefUpdateListener);
if (Services.prefs.getBoolPref("security.ssl.errorReporting.automatic")) {
resolve();
} else {
reject();
}
};
mm.addMessageListener("ssler-test:AutoPrefUpdated", prefUpdateListener);
});
mm.sendAsyncMessage("ssler-test:SetAutoPref",{value:true});
yield prefEnabled;
// ensure un-setting automatic, when set, works
let prefDisabled = new Promise(function(resolve, reject){
let prefUpdateListener = function () {
mm.removeMessageListener("ssler-test:AutoPrefUpdated", prefUpdateListener);
if (!Services.prefs.getBoolPref("security.ssl.errorReporting.automatic")) {
resolve();
} else {
reject();
}
};
mm.addMessageListener("ssler-test:AutoPrefUpdated", prefUpdateListener);
});
mm.sendAsyncMessage("ssler-test:SetAutoPref",{value:false});
yield prefDisabled;
gBrowser.removeTab(tab);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("security.cert_pinning.enforcement_level");
cleanup();
});
// test that manual report sending (with button clicks) works
var testSendReportManual = function*(testURL, suffix) {
setup();
Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", true);
Services.prefs.setCharPref("security.ssl.errorReporting.url",
"https://example.com/browser/browser/base/content/test/general/pinning_reports.sjs?" + suffix);
add_task(function* test_send_report_neterror() {
yield testSendReportAutomatically(URL_BAD_CHAIN, "succeed", "neterror");
yield testSendReportAutomatically(URL_NO_CERT, "nocert", "neterror");
yield testSendReportFailRetry(URL_NO_CERT, "nocert", "neterror");
yield testSetAutomatic(URL_NO_CERT, "nocert", "neterror");
});
let tab = gBrowser.addTab(testURL, {skipAnimation: true});
add_task(function* test_send_report_certerror() {
yield testSendReportAutomatically(URL_BAD_CERT, "badcert", "certerror");
yield testSendReportFailRetry(URL_BAD_CERT, "badcert", "certerror");
yield testSetAutomatic(URL_BAD_CERT, "badcert", "certerror");
});
add_task(function* test_send_disabled() {
Services.prefs.setBoolPref(PREF_REPORT_ENABLED, false);
Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, true);
Services.prefs.setCharPref(PREF_REPORT_URL, "https://example.com/invalid");
// Check with enabled=false but automatic=true.
yield testSendReportDisabled(URL_NO_CERT, "neterror");
yield testSendReportDisabled(URL_BAD_CERT, "certerror");
Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, false);
// Check again with both prefs false.
yield testSendReportDisabled(URL_NO_CERT, "neterror");
yield testSendReportDisabled(URL_BAD_CERT, "certerror");
cleanup();
});
function* testSendReportAutomatically(testURL, suffix, errorURISuffix) {
Services.prefs.setBoolPref(PREF_REPORT_ENABLED, true);
Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, true);
Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix);
// Add a tab and wait until it's loaded.
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
let browser = tab.linkedBrowser;
let mm = browser.messageManager;
mm.loadFrameScript(ROOT + "browser_ssl_error_reports_content.js", true);
gBrowser.selectedTab = tab;
// Load the page and wait for the error report submission.
let promiseReport = createErrorReportPromise(browser);
browser.loadURI(testURL);
yield promiseReport;
ok(true, "SSL error report submitted successfully");
// ensure we have the correct error message from about:neterror
let netError = createNetworkErrorMessagePromise(browser);
yield netError;
netError.then(function(val){
is(val.startsWith("An error occurred during a connection to"), true,
"ensure the correct error message came from about:neterror");
});
let btn = browser.contentDocument.getElementById("reportCertificateError");
let deferredReportSucceeds = Promise.defer();
// ensure we see the correct statuses in the correct order...
let statusListener = function() {
let active = false;
return function(message) {
switch(message.data.reportStatus) {
case "activity":
if (!active) {
active = true;
}
break;
case "complete":
mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener);
if (active) {
deferredReportSucceeds.resolve(message.data.reportStatus);
} else {
deferredReportSucceeds.reject('activity should be seen before success');
}
break;
case "error":
mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener);
deferredReportSucceeds.reject();
break;
}
};
}();
mm.addMessageListener("ssler-test:SSLErrorReportStatus", statusListener);
// ... once the button is clicked, that is
mm.sendAsyncMessage("ssler-test:SendBtnClick",{});
yield deferredReportSucceeds.promise;
// Check that we loaded the right error page.
yield checkErrorPage(browser, errorURISuffix);
// Cleanup.
gBrowser.removeTab(tab);
cleanup();
};
// test that automatic sending works
add_task(function* test_send_report_auto() {
setup();
Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", true);
Services.prefs.setBoolPref("security.ssl.errorReporting.automatic", true);
Services.prefs.setCharPref("security.ssl.errorReporting.url", "https://example.com/browser/browser/base/content/test/general/pinning_reports.sjs?succeed");
function* testSendReportFailRetry(testURL, suffix, errorURISuffix) {
try {
yield testSendReportAutomatically(testURL, "error", errorURISuffix);
ok(false, "sending a report should have failed");
} catch (err) {
ok(err, "saw a failure notification");
}
let tab = gBrowser.addTab(badChainURL, {skipAnimation: true});
let browser = tab.linkedBrowser;
let mm = browser.messageManager;
mm.loadFrameScript(ROOT + "browser_ssl_error_reports_content.js", true);
Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix);
gBrowser.selectedTab = tab;
// Ensure the error page loads
let netError = createNetworkErrorMessagePromise(browser);
yield netError;
let reportWillStart = Promise.defer();
let startListener = function() {
mm.removeMessageListener("Browser:SendSSLErrorReport", startListener);
reportWillStart.resolve();
};
mm.addMessageListener("Browser:SendSSLErrorReport", startListener);
let deferredReportSucceeds = Promise.defer();
let statusListener = function(message) {
switch(message.data.reportStatus) {
case "complete":
mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener);
deferredReportSucceeds.resolve(message.data.reportStatus);
break;
case "error":
mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener);
deferredReportSucceeds.reject();
break;
}
};
mm.addMessageListener("ssler-test:SSLErrorReportStatus", statusListener);
// Ensure the report is sent with no interaction
yield deferredReportSucceeds.promise;
gBrowser.removeTab(tab);
cleanup();
});
// test that an error is shown if there's a problem with the report server
add_task(function* test_send_report_error() {
setup();
// set up prefs so error send is automatic and an error will occur
Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", true);
Services.prefs.setBoolPref("security.ssl.errorReporting.automatic", true);
Services.prefs.setCharPref("security.ssl.errorReporting.url", "https://example.com/browser/browser/base/content/test/general/pinning_reports.sjs?error");
// load the test URL so error page is seen
let tab = gBrowser.addTab(badChainURL, {skipAnimation: true});
let browser = tab.linkedBrowser;
gBrowser.selectedTab = tab;
let mm = browser.messageManager;
mm.loadFrameScript(ROOT + "browser_ssl_error_reports_content.js", true);
let reportErrors = new Promise(function(resolve, reject) {
let statusListener = function(message) {
switch(message.data.reportStatus) {
case "complete":
reject(message.data.reportStatus);
mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener);
break;
case "error":
resolve(message.data.reportStatus);
mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener);
break;
}
};
mm.addMessageListener("ssler-test:SSLErrorReportStatus", statusListener);
let browser = gBrowser.selectedBrowser;
let promiseReport = createErrorReportPromise(browser);
let promiseRetry = ContentTask.spawn(browser, null, function* () {
content.document.getElementById("reportCertificateErrorRetry").click();
});
// check that errors are sent
yield reportErrors;
yield Promise.all([promiseReport, promiseRetry]);
ok(true, "SSL error report submitted successfully");
gBrowser.removeTab(tab);
// Cleanup.
gBrowser.removeCurrentTab();
cleanup();
});
}
add_task(function* test_send_report_disabled() {
setup();
Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", false);
Services.prefs.setCharPref("security.ssl.errorReporting.url", "https://offdomain.com");
function* testSetAutomatic(testURL, suffix, errorURISuffix) {
Services.prefs.setBoolPref(PREF_REPORT_ENABLED, true);
Services.prefs.setBoolPref(PREF_REPORT_AUTOMATIC, false);
Services.prefs.setCharPref(PREF_REPORT_URL, URL_REPORTS + suffix);
let tab = gBrowser.addTab(badChainURL, {skipAnimation: true});
// Add a tab and wait until it's loaded.
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
let browser = tab.linkedBrowser;
let mm = browser.messageManager;
mm.loadFrameScript(ROOT + "browser_ssl_error_reports_content.js", true);
gBrowser.selectedTab = tab;
// Load the page.
browser.loadURI(testURL);
yield promiseErrorPageLoaded(browser);
// Ensure we have an error page
let netError = createNetworkErrorMessagePromise(browser);
yield netError;
// Check that we loaded the right error page.
yield checkErrorPage(browser, errorURISuffix);
let reportErrors = new Promise(function(resolve, reject) {
let statusListener = function(message) {
switch(message.data.reportStatus) {
case "complete":
mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener);
reject(message.data.reportStatus);
break;
case "error":
mm.removeMessageListener("ssler-test:SSLErrorReportStatus", statusListener);
resolve(message.data.reportStatus);
break;
}
};
mm.addMessageListener("ssler-test:SSLErrorReportStatus", statusListener);
// Click the checkbox, enable automatic error reports.
let promiseReport = createErrorReportPromise(browser);
yield ContentTask.spawn(browser, null, function* () {
content.document.getElementById("automaticallyReportInFuture").click();
});
// click the button
mm.sendAsyncMessage("ssler-test:SendBtnClick",{forceUI:true});
// Wait for the error report submission.
yield promiseReport;
ok(true, "SSL error report submitted successfully");
// check we get an error
yield reportErrors;
let isAutomaticReportingEnabled = () =>
Services.prefs.getBoolPref(PREF_REPORT_AUTOMATIC);
// Check that the pref was flipped.
ok(isAutomaticReportingEnabled(), "automatic SSL report submission enabled");
// Disable automatic error reports.
yield ContentTask.spawn(browser, null, function* () {
content.document.getElementById("automaticallyReportInFuture").click();
});
// Check that the pref was flipped.
ok(!isAutomaticReportingEnabled(), "automatic SSL report submission disabled");
// Cleanup.
gBrowser.removeTab(tab);
cleanup();
});
function setup() {
// ensure the relevant prefs are set
enabledPref = Services.prefs.getBoolPref("security.ssl.errorReporting.enabled");
automaticPref = Services.prefs.getBoolPref("security.ssl.errorReporting.automatic");
urlPref = Services.prefs.getCharPref("security.ssl.errorReporting.url");
enforcement_level = Services.prefs.getIntPref("security.cert_pinning.enforcement_level");
Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2);
}
function cleanup() {
// reset prefs for other tests in the run
Services.prefs.setBoolPref("security.ssl.errorReporting.enabled", enabledPref);
Services.prefs.setBoolPref("security.ssl.errorReporting.automatic", automaticPref);
Services.prefs.setCharPref("security.ssl.errorReporting.url", urlPref);
function* testSendReportDisabled(testURL, errorURISuffix) {
// Add a tab and wait until it's loaded.
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "about:blank");
let browser = tab.linkedBrowser;
// Load the page.
browser.loadURI(testURL);
yield promiseErrorPageLoaded(browser);
// Check that we loaded the right error page.
yield checkErrorPage(browser, errorURISuffix);
// Check that the error reporting section is hidden.
let hidden = yield ContentTask.spawn(browser, null, function* () {
let section = content.document.getElementById("certificateErrorReporting");
return content.getComputedStyle(section).display == "none";
});
ok(hidden, "error reporting section should be hidden");
// Cleanup.
gBrowser.removeTab(tab);
}
function createErrorReportPromise(browser) {
return ContentTask.spawn(browser, null, function* () {
let type = "Browser:SSLErrorReportStatus";
let active = false;
yield new Promise((resolve, reject) => {
addMessageListener(type, function onReportStatus(message) {
switch (message.data.reportStatus) {
case "activity":
active = true;
break;
case "complete":
removeMessageListener(type, onReportStatus);
if (active) {
resolve(message.data.reportStatus);
} else {
reject("activity should be seen before success");
}
break;
case "error":
removeMessageListener(type, onReportStatus);
reject("sending the report failed");
break;
}
});
});
});
}
function promiseErrorPageLoaded(browser) {
return new Promise(resolve => {
browser.addEventListener("DOMContentLoaded", function onLoad() {
browser.removeEventListener("DOMContentLoaded", onLoad, false, true);
resolve();
}, false, true);
});
}
function checkErrorPage(browser, suffix) {
return ContentTask.spawn(browser, null, function* () {
return content.document.documentURI;
}).then(uri => {
ok(uri.startsWith(`about:${suffix}`), "correct error page loaded");
});
}

View File

@ -1,25 +0,0 @@
addMessageListener("Browser:SSLErrorReportStatus", function(message) {
sendSyncMessage("ssler-test:SSLErrorReportStatus", {reportStatus:message.data.reportStatus});
});
addMessageListener("ssler-test:SetAutoPref", function(message) {
let checkbox = content.document.getElementById("automaticallyReportInFuture");
// we use "click" because otherwise the 'changed' event will not fire
if (checkbox.checked != message.data.value) {
checkbox.click();
}
sendSyncMessage("ssler-test:AutoPrefUpdated", {});
});
addMessageListener("ssler-test:SendBtnClick", function(message) {
if (message.data && message.data.forceUI) {
content.dispatchEvent(new content.CustomEvent("AboutNetErrorOptions",
{
detail: "{\"enabled\": true, \"automatic\": false}"
}));
}
let btn = content.document.getElementById("reportCertificateError");
btn.click();
});

View File

@ -0,0 +1,79 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
"@mozilla.org/mediaManagerService;1",
"nsIMediaManagerService");
const kObservedTopics = [
"getUserMedia:response:allow",
"getUserMedia:revoke",
"getUserMedia:response:deny",
"getUserMedia:request",
"recording-device-events",
"recording-window-ended"
];
var gObservedTopics = {};
function observer(aSubject, aTopic, aData) {
if (!(aTopic in gObservedTopics))
gObservedTopics[aTopic] = 1;
else
++gObservedTopics[aTopic];
}
kObservedTopics.forEach(topic => {
Services.obs.addObserver(observer, topic, false);
});
addMessageListener("Test:ExpectObserverCalled", ({data}) => {
sendAsyncMessage("Test:ExpectObserverCalled:Reply",
{count: gObservedTopics[data]});
if (data in gObservedTopics)
--gObservedTopics[data];
});
addMessageListener("Test:TodoObserverNotCalled", ({data}) => {
sendAsyncMessage("Test:TodoObserverNotCalled:Reply",
{count: gObservedTopics[data]});
if (gObservedTopics[data] == 1)
gObservedTopics[data] = 0;
});
addMessageListener("Test:ExpectNoObserverCalled", data => {
sendAsyncMessage("Test:ExpectNoObserverCalled:Reply", gObservedTopics);
gObservedTopics = {};
});
function _getMediaCaptureState() {
let hasVideo = {};
let hasAudio = {};
MediaManagerService.mediaCaptureWindowState(content, hasVideo, hasAudio);
if (hasVideo.value && hasAudio.value)
return "CameraAndMicrophone";
if (hasVideo.value)
return "Camera";
if (hasAudio.value)
return "Microphone";
return "none";
}
addMessageListener("Test:GetMediaCaptureState", data => {
sendAsyncMessage("Test:MediaCaptureState", _getMediaCaptureState());
});
addMessageListener("Test:WaitForObserverCall", ({data}) => {
let topic = data;
Services.obs.addObserver(function observer() {
sendAsyncMessage("Test:ObserverCalled", topic);
Services.obs.removeObserver(observer, topic);
if (kObservedTopics.indexOf(topic) != -1) {
if (!(topic in gObservedTopics))
gObservedTopics[topic] = -1;
else
--gObservedTopics[topic];
}
}, topic, false);
});

View File

@ -0,0 +1,228 @@
/* 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/. */
const PREF_PERMISSION_FAKE = "media.navigator.permission.fake";
function _mm() {
return gBrowser.selectedBrowser.messageManager;
}
function promiseObserverCalled(aTopic) {
return new Promise(resolve => {
let mm = _mm();
mm.addMessageListener("Test:ObserverCalled", function listener({data}) {
if (data == aTopic) {
ok(true, "got " + aTopic + " notification");
mm.removeMessageListener("Test:ObserverCalled", listener);
resolve();
}
});
mm.sendAsyncMessage("Test:WaitForObserverCall", aTopic);
});
}
function expectObserverCalled(aTopic) {
return new Promise(resolve => {
let mm = _mm();
mm.addMessageListener("Test:ExpectObserverCalled:Reply",
function listener({data}) {
is(data.count, 1, "expected notification " + aTopic);
mm.removeMessageListener("Test:ExpectObserverCalled:Reply", listener);
resolve();
});
mm.sendAsyncMessage("Test:ExpectObserverCalled", aTopic);
});
}
function expectNoObserverCalled() {
return new Promise(resolve => {
let mm = _mm();
mm.addMessageListener("Test:ExpectNoObserverCalled:Reply",
function listener({data}) {
mm.removeMessageListener("Test:ExpectNoObserverCalled:Reply", listener);
for (let topic in data) {
if (data[topic])
is(data[topic], 0, topic + " notification unexpected");
}
resolve();
});
mm.sendAsyncMessage("Test:ExpectNoObserverCalled");
});
}
function promiseTodoObserverNotCalled(aTopic) {
return new Promise(resolve => {
let mm = _mm();
mm.addMessageListener("Test:TodoObserverNotCalled:Reply",
function listener({data}) {
mm.removeMessageListener("Test:TodoObserverNotCalled:Reply", listener);
resolve(data.count);
});
mm.sendAsyncMessage("Test:TodoObserverNotCalled", aTopic);
});
}
function promiseMessage(aMessage, aAction) {
let deferred = Promise.defer();
content.addEventListener("message", function messageListener(event) {
content.removeEventListener("message", messageListener);
is(event.data, aMessage, "received " + aMessage);
if (event.data == aMessage)
deferred.resolve();
else
deferred.reject();
});
if (aAction)
aAction();
return deferred.promise;
}
function promisePopupNotificationShown(aName, aAction) {
let deferred = Promise.defer();
PopupNotifications.panel.addEventListener("popupshown", function popupNotifShown() {
PopupNotifications.panel.removeEventListener("popupshown", popupNotifShown);
ok(!!PopupNotifications.getNotification(aName), aName + " notification shown");
ok(PopupNotifications.isPanelOpen, "notification panel open");
ok(!!PopupNotifications.panel.firstChild, "notification panel populated");
deferred.resolve();
});
if (aAction)
aAction();
return deferred.promise;
}
function promisePopupNotification(aName) {
let deferred = Promise.defer();
waitForCondition(() => PopupNotifications.getNotification(aName),
() => {
ok(!!PopupNotifications.getNotification(aName),
aName + " notification appeared");
deferred.resolve();
}, "timeout waiting for popup notification " + aName);
return deferred.promise;
}
function promiseNoPopupNotification(aName) {
let deferred = Promise.defer();
waitForCondition(() => !PopupNotifications.getNotification(aName),
() => {
ok(!PopupNotifications.getNotification(aName),
aName + " notification removed");
deferred.resolve();
}, "timeout waiting for popup notification " + aName + " to disappear");
return deferred.promise;
}
const kActionAlways = 1;
const kActionDeny = 2;
const kActionNever = 3;
function activateSecondaryAction(aAction) {
let notification = PopupNotifications.panel.firstChild;
notification.button.focus();
let popup = notification.menupopup;
popup.addEventListener("popupshown", function () {
popup.removeEventListener("popupshown", arguments.callee, false);
// Press 'down' as many time as needed to select the requested action.
while (aAction--)
EventUtils.synthesizeKey("VK_DOWN", {});
// Activate
EventUtils.synthesizeKey("VK_RETURN", {});
}, false);
// One down event to open the popup
EventUtils.synthesizeKey("VK_DOWN",
{ altKey: !navigator.platform.includes("Mac") });
}
function getMediaCaptureState() {
return new Promise(resolve => {
let mm = _mm();
mm.addMessageListener("Test:MediaCaptureState", ({data}) => {
resolve(data);
});
mm.sendAsyncMessage("Test:GetMediaCaptureState");
});
}
function promiseRequestDevice(aRequestAudio, aRequestVideo, aFrameId) {
info("requesting devices");
return ContentTask.spawn(gBrowser.selectedBrowser,
{aRequestAudio, aRequestVideo, aFrameId},
function*(args) {
let global = content.wrappedJSObject;
if (args.aFrameId)
global = global.document.getElementById(args.aFrameId).contentWindow;
global.requestDevice(args.aRequestAudio, args.aRequestVideo);
});
}
function* closeStream(aAlreadyClosed, aFrameId) {
yield expectNoObserverCalled();
let promise;
if (!aAlreadyClosed)
promise = promiseObserverCalled("recording-device-events");
info("closing the stream");
yield ContentTask.spawn(gBrowser.selectedBrowser, aFrameId, function*(aFrameId) {
let global = content.wrappedJSObject;
if (aFrameId)
global = global.document.getElementById(aFrameId).contentWindow;
global.closeStream();
});
if (!aAlreadyClosed)
yield promise;
yield promiseNoPopupNotification("webRTC-sharingDevices");
if (!aAlreadyClosed)
yield expectObserverCalled("recording-window-ended");
yield* assertWebRTCIndicatorStatus(null);
}
function checkDeviceSelectors(aAudio, aVideo) {
let micSelector = document.getElementById("webRTC-selectMicrophone");
if (aAudio)
ok(!micSelector.hidden, "microphone selector visible");
else
ok(micSelector.hidden, "microphone selector hidden");
let cameraSelector = document.getElementById("webRTC-selectCamera");
if (aVideo)
ok(!cameraSelector.hidden, "camera selector visible");
else
ok(cameraSelector.hidden, "camera selector hidden");
}
function* checkSharingUI(aExpected) {
yield promisePopupNotification("webRTC-sharingDevices");
yield* assertWebRTCIndicatorStatus(aExpected);
}
function* checkNotSharing() {
is((yield getMediaCaptureState()), "none", "expected nothing to be shared");
ok(!PopupNotifications.getNotification("webRTC-sharingDevices"),
"no webRTC-sharingDevices popup notification");
yield* assertWebRTCIndicatorStatus(null);
}

View File

@ -61,6 +61,20 @@ function handleRequest(request, response) {
return;
}
// if all is as expected, send the 201 the client expects
response.setStatusLine("1.1", 201, "Created");
response.write("<html>OK</html>");
break;
case "badcert":
report = parseReport(request);
certChain = report.failedCertChain;
if (!certChain || certChain.length != 2) {
response.setStatusLine("1.1", 500, "Server error");
response.write("<html>The report contained an unexpected chain</html>");
return;
}
// if all is as expected, send the 201 the client expects
response.setStatusLine("1.1", 201, "Created");
response.write("<html>OK</html>");

View File

@ -1342,7 +1342,9 @@ var CustomizableUIInternal = {
}
let tooltip = this.getLocalizedProperty(aWidget, "tooltiptext", additionalTooltipArguments);
node.setAttribute("tooltiptext", tooltip);
if (tooltip) {
node.setAttribute("tooltiptext", tooltip);
}
node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional");
let commandHandler = this.handleWidgetCommand.bind(this, aWidget, node);
@ -1385,6 +1387,8 @@ var CustomizableUIInternal = {
},
getLocalizedProperty: function(aWidget, aProp, aFormatArgs, aDef) {
const kReqStringProps = ["label"];
if (typeof aWidget == "string") {
aWidget = gPalette.get(aWidget);
}
@ -1395,7 +1399,7 @@ var CustomizableUIInternal = {
// Let widgets pass their own string identifiers or strings, so that
// we can use strings which aren't the default (in case string ids change)
// and so that non-builtin-widgets can also provide labels, tooltips, etc.
if (aWidget[aProp]) {
if (aWidget[aProp] != null) {
name = aWidget[aProp];
// By using this as the default, if a widget provides a full string rather
// than a string ID for localization, we will fall back to that string
@ -1412,7 +1416,9 @@ var CustomizableUIInternal = {
}
return gWidgetsBundle.GetStringFromName(name) || def;
} catch(ex) {
if (!def) {
// If an empty string was explicitly passed, treat it as an actual
// value rather than a missing property.
if (!def && (name != "" || kReqStringProps.includes(aProp))) {
ERROR("Could not localize property '" + name + "'.");
}
}
@ -2336,6 +2342,7 @@ var CustomizableUIInternal = {
this.wrapWidgetEventHandler("onBeforeCreated", widget);
this.wrapWidgetEventHandler("onClick", widget);
this.wrapWidgetEventHandler("onCreated", widget);
this.wrapWidgetEventHandler("onDestroyed", widget);
if (widget.type == "button") {
widget.onCommand = typeof aData.onCommand == "function" ?
@ -2439,6 +2446,9 @@ var CustomizableUIInternal = {
}
}
}
if (widgetNode && widget.onDestroyed) {
widget.onDestroyed(window.document);
}
}
gPalette.delete(aWidgetId);
@ -3172,6 +3182,11 @@ this.CustomizableUI = {
* - onCreated(aNode): Attached to all widgets; a function that will be invoked
* whenever the widget has a DOM node constructed, passing the
* constructed node as an argument.
* - onDestroyed(aDoc): Attached to all non-custom widgets; a function that
* will be invoked after the widget has a DOM node destroyed,
* passing the document from which it was removed. This is
* useful especially for 'view' type widgets that need to
* cleanup after views that were constructed on the fly.
* - onCommand(aEvt): Only useful for button widgets; a function that will be
* invoked when the user activates the button.
* - onClick(aEvt): Attached to all widgets; a function that will be invoked

View File

@ -329,6 +329,7 @@ const PanelUI = {
evt.initCustomEvent("ViewShowing", true, true, viewNode);
viewNode.dispatchEvent(evt);
if (evt.defaultPrevented) {
aAnchor.open = false;
return;
}

View File

@ -326,6 +326,12 @@
]]></body>
</method>
<method name="_shouldSetPosition">
<body><![CDATA[
return this.getAttribute("nosubviews") == "true";
]]></body>
</method>
<method name="_shouldSetHeight">
<body><![CDATA[
return this.getAttribute("nosubviews") != "true";
@ -345,6 +351,19 @@
this.ignoreMutations = false;
]]></body>
</method>
<method name="_adjustContainerHeight">
<body><![CDATA[
if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
let height;
if (this.showingSubViewAsMainView) {
height = this._heightOfSubview(this._mainView);
} else {
height = this._mainView.scrollHeight;
}
this._viewContainer.style.height = height + "px";
}
]]></body>
</method>
<method name="_syncContainerWithSubView">
<body><![CDATA[
// Check that this panel is still alive:
@ -361,18 +380,16 @@
<method name="_syncContainerWithMainView">
<body><![CDATA[
// Check that this panel is still alive:
if (!this._panel || !this._panel.parentNode || !this._shouldSetHeight()) {
if (!this._panel || !this._panel.parentNode) {
return;
}
if (!this.ignoreMutations && !this.showingSubView && !this._transitioning) {
let height;
if (this.showingSubViewAsMainView) {
height = this._heightOfSubview(this._mainView);
} else {
height = this._mainView.scrollHeight;
}
this._viewContainer.style.height = height + "px";
if (this._shouldSetPosition()) {
this._panel.adjustArrowPosition();
}
if (this._shouldSetHeight()) {
this._adjustContainerHeight();
}
]]></body>
</method>

View File

@ -2,13 +2,14 @@
"extends": "../../../toolkit/components/extensions/.eslintrc",
"globals": {
"AllWindowEvents": true,
"currentWindow": true,
"EventEmitter": true,
"IconDetails": true,
"openPanel": true,
"makeWidgetId": true,
"PanelPopup": true,
"TabContext": true,
"AllWindowEvents": true,
"ViewPopup": true,
"WindowEventManager": true,
"WindowListManager": true,
"WindowManager": true,

View File

@ -13,6 +13,8 @@ var {
runSafe,
} = ExtensionUtils;
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// WeakMap[Extension -> BrowserAction]
var browserActionMap = new WeakMap();
@ -24,7 +26,10 @@ function browserActionOf(extension) {
// as the associated popup.
function BrowserAction(options, extension) {
this.extension = extension;
this.id = makeWidgetId(extension.id) + "-browser-action";
let widgetId = makeWidgetId(extension.id);
this.id = `${widgetId}-browser-action`;
this.viewId = `PanelUI-webext-${widgetId}-browser-action-view`;
this.widget = null;
this.tabManager = TabManager.for(extension);
@ -37,7 +42,7 @@ function BrowserAction(options, extension) {
this.defaults = {
enabled: true,
title: title,
title: title || extension.name,
badgeText: "",
badgeBackgroundColor: null,
icon: IconDetails.normalize({ path: options.default_icon }, extension,
@ -55,31 +60,60 @@ BrowserAction.prototype = {
build() {
let widget = CustomizableUI.createWidget({
id: this.id,
type: "custom",
viewId: this.viewId,
type: "view",
removable: true,
label: this.defaults.title || this.extension.name,
tooltiptext: this.defaults.title || "",
defaultArea: CustomizableUI.AREA_NAVBAR,
onBuild: document => {
let node = document.createElement("toolbarbutton");
node.id = this.id;
node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional badged-button");
onBeforeCreated: document => {
let view = document.createElementNS(XUL_NS, "panelview");
view.id = this.viewId;
view.setAttribute("flex", "1");
document.getElementById("PanelUI-multiView").appendChild(view);
},
onDestroyed: document => {
let view = document.getElementById(this.viewId);
if (view) {
view.remove();
}
},
onCreated: node => {
node.classList.add("badged-button");
node.setAttribute("constrain-size", "true");
this.updateButton(node, this.defaults);
},
onViewShowing: event => {
let document = event.target.ownerDocument;
let tabbrowser = document.defaultView.gBrowser;
node.addEventListener("command", event => { // eslint-disable-line mozilla/balanced-listeners
let tab = tabbrowser.selectedTab;
let popup = this.getProperty(tab, "popup");
this.tabManager.addActiveTabPermission(tab);
if (popup) {
this.togglePopup(node, popup);
} else {
this.emit("click");
}
});
let tab = tabbrowser.selectedTab;
let popupURL = this.getProperty(tab, "popup");
this.tabManager.addActiveTabPermission(tab);
return node;
// If the widget has a popup URL defined, we open a popup, but do not
// dispatch a click event to the extension.
// If it has no popup URL defined, we dispatch a click event, but do not
// open a popup.
if (popupURL) {
try {
new ViewPopup(this.extension, event.target, popupURL);
} catch (e) {
Cu.reportError(e);
event.preventDefault();
}
} else {
// This isn't not a hack, but it seems to provide the correct behavior
// with the fewest complications.
event.preventDefault();
this.emit("click");
}
},
});
@ -89,22 +123,12 @@ BrowserAction.prototype = {
this.widget = widget;
},
togglePopup(node, popupResource) {
openPanel(node, popupResource, this.extension);
},
// Update the toolbar button |node| with the tab context data
// in |tabData|.
updateButton(node, tabData) {
if (tabData.title) {
node.setAttribute("tooltiptext", tabData.title);
node.setAttribute("label", tabData.title);
node.setAttribute("aria-label", tabData.title);
} else {
node.removeAttribute("tooltiptext");
node.removeAttribute("label");
node.removeAttribute("aria-label");
}
let title = tabData.title || this.extension.name;
node.setAttribute("tooltiptext", title);
node.setAttribute("label", title);
if (tabData.badgeText) {
node.setAttribute("badge", tabData.badgeText);
@ -162,8 +186,10 @@ BrowserAction.prototype = {
setProperty(tab, prop, value) {
if (tab == null) {
this.defaults[prop] = value;
} else {
} else if (value != null) {
this.tabContext.get(tab)[prop] = value;
} else {
delete this.tabContext.get(tab)[prop];
}
this.updateOnChange(tab);
@ -226,7 +252,13 @@ extensions.registerSchemaAPI("browserAction", null, (extension, context) => {
setTitle: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
browserActionOf(extension).setProperty(tab, "title", details.title);
let title = details.title;
// Clear the tab-specific title when given a null string.
if (tab && title == "") {
title = null;
}
browserActionOf(extension).setProperty(tab, "title", title);
},
getTitle: function(details, callback) {

View File

@ -28,7 +28,7 @@ function PageAction(options, extension) {
this.defaults = {
show: false,
title: title,
title: title || extension.name,
icon: IconDetails.normalize({ path: options.default_icon }, extension,
null, true),
popup: popup && extension.baseURI.resolve(popup),
@ -58,7 +58,12 @@ PageAction.prototype = {
// If |tab| is currently selected, updates the page action button to
// reflect the new value.
setProperty(tab, prop, value) {
this.tabContext.get(tab)[prop] = value;
if (value != null) {
this.tabContext.get(tab)[prop] = value;
} else {
delete this.tabContext.get(tab)[prop];
}
if (tab.selected) {
this.updateButton(tab.ownerDocument.defaultView);
}
@ -84,13 +89,9 @@ PageAction.prototype = {
if (tabData.show) {
// Update the title and icon only if the button is visible.
if (tabData.title) {
button.setAttribute("tooltiptext", tabData.title);
button.setAttribute("aria-label", tabData.title);
} else {
button.removeAttribute("tooltiptext");
button.removeAttribute("aria-label");
}
let title = tabData.title || this.extension.name;
button.setAttribute("tooltiptext", title);
button.setAttribute("aria-label", title);
let icon = IconDetails.getURL(tabData.icon, window, this.extension);
button.setAttribute("src", icon);
@ -137,12 +138,16 @@ PageAction.prototype = {
// the any click listeners in the add-on.
handleClick(window) {
let tab = window.gBrowser.selectedTab;
let popup = this.tabContext.get(tab).popup;
let popupURL = this.tabContext.get(tab).popup;
this.tabManager.addActiveTabPermission(tab);
if (popup) {
openPanel(this.getButton(window), popup, this.extension);
// If the widget has a popup URL defined, we open a popup, but do not
// dispatch a click event to the extension.
// If it has no popup URL defined, we dispatch a click event, but do not
// open a popup.
if (popupURL) {
new PanelPopup(this.extension, this.getButton(window), popupURL);
} else {
this.emit("click", tab);
}
@ -213,7 +218,9 @@ extensions.registerSchemaAPI("pageAction", null, (extension, context) => {
setTitle(details) {
let tab = TabManager.getTab(details.tabId);
PageAction.for(extension).setProperty(tab, "title", details.title);
// Clear the tab-specific title when given a null string.
PageAction.for(extension).setProperty(tab, "title", details.title || null);
},
getTitle(details, callback) {

View File

@ -2,12 +2,16 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
Cu.import("resource://gre/modules/AddonManager.jsm");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const INTEGER = /^[1-9]\d*$/;
var {
@ -126,103 +130,203 @@ global.makeWidgetId = id => {
return id.replace(/[^a-z0-9_-]/g, "_");
};
// Open a panel anchored to the given node, containing a browser opened
// to the given URL, owned by the given extension. If |popupURL| is not
// an absolute URL, it is resolved relative to the given extension's
// base URL.
global.openPanel = (node, popupURL, extension) => {
let document = node.ownerDocument;
class BasePopup {
constructor(extension, viewNode, popupURL) {
let popupURI = Services.io.newURI(popupURL, null, extension.baseURI);
let popupURI = Services.io.newURI(popupURL, null, extension.baseURI);
Services.scriptSecurityManager.checkLoadURIWithPrincipal(
extension.principal, popupURI,
Services.scriptSecurityManager.DISALLOW_SCRIPT);
Services.scriptSecurityManager.checkLoadURIWithPrincipal(
extension.principal, popupURI,
Services.scriptSecurityManager.DISALLOW_SCRIPT);
this.extension = extension;
this.popupURI = popupURI;
this.viewNode = viewNode;
this.window = viewNode.ownerDocument.defaultView;
let panel = document.createElement("panel");
panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
panel.setAttribute("class", "browser-extension-panel");
panel.setAttribute("type", "arrow");
panel.setAttribute("role", "group");
this.contentReady = new Promise(resolve => {
this._resolveContentReady = resolve;
});
let anchor;
if (node.localName == "toolbarbutton") {
// Toolbar buttons are a special case. The panel becomes a child of
// the button, and is anchored to the button's icon.
node.appendChild(panel);
anchor = document.getAnonymousElementByAttribute(node, "class", "toolbarbutton-icon");
} else {
// In all other cases, the panel is anchored to the target node
// itself, and is a child of a popupset node.
document.getElementById("mainPopupSet").appendChild(panel);
anchor = node;
this.viewNode.addEventListener(this.DESTROY_EVENT, this);
this.browser = null;
this.browserReady = this.createBrowser(viewNode, popupURI);
}
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
let browser = document.createElementNS(XUL_NS, "browser");
browser.setAttribute("type", "content");
browser.setAttribute("disableglobalhistory", "true");
panel.appendChild(browser);
destroy() {
this.browserReady.then(() => {
this.browser.removeEventListener("load", this, true);
this.browser.removeEventListener("DOMTitleChanged", this, true);
this.browser.removeEventListener("DOMWindowClose", this, true);
let titleChangedListener = () => {
panel.setAttribute("aria-label", browser.contentTitle);
};
this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
let context;
let popuphidden = () => {
panel.removeEventListener("popuphidden", popuphidden);
browser.removeEventListener("DOMTitleChanged", titleChangedListener, true);
context.unload();
panel.remove();
};
panel.addEventListener("popuphidden", popuphidden);
this.context.unload();
this.browser.remove();
let loadListener = () => {
panel.removeEventListener("load", loadListener);
context = new ExtensionPage(extension, {
type: "popup",
contentWindow: browser.contentWindow,
uri: popupURI,
docShell: browser.docShell,
this.browser = null;
this.viewNode = null;
this.context = null;
});
GlobalManager.injectInDocShell(browser.docShell, extension, context);
browser.setAttribute("src", context.uri.spec);
}
let contentLoadListener = event => {
if (event.target != browser.contentDocument) {
return;
}
browser.removeEventListener("load", contentLoadListener, true);
// Returns the name of the event fired on `viewNode` when the popup is being
// destroyed. This must be implemented by every subclass.
get DESTROY_EVENT() {
throw new Error("Not implemented");
}
let contentViewer = browser.docShell.contentViewer;
let width = {}, height = {};
try {
contentViewer.getContentSize(width, height);
[width, height] = [width.value, height.value];
} catch (e) {
// getContentSize can throw
[width, height] = [400, 400];
}
handleEvent(event) {
switch (event.type) {
case this.DESTROY_EVENT:
this.destroy();
break;
let window = document.defaultView;
width /= window.devicePixelRatio;
height /= window.devicePixelRatio;
width = Math.min(width, 800);
height = Math.min(height, 800);
case "DOMWindowClose":
if (event.target === this.browser.contentWindow) {
event.preventDefault();
this.closePopup();
}
break;
browser.setAttribute("width", width);
browser.setAttribute("height", height);
case "DOMTitleChanged":
this.viewNode.setAttribute("aria-label", this.browser.contentTitle);
break;
panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
};
browser.addEventListener("load", contentLoadListener, true);
case "load":
// We use a capturing listener, so we get this event earlier than any
// load listeners in the content page. Resizing after a timeout ensures
// that we calculate the size after the entire event cycle has completed
// (unless someone spins the event loop, anyway), and hopefully after
// the content has made any modifications.
//
// In the future, to match Chrome's behavior, we'll need to update this
// dynamically, probably in response to MozScrolledAreaChanged events.
this.window.setTimeout(() => this.resizeBrowser(), 0);
break;
}
}
browser.addEventListener("DOMTitleChanged", titleChangedListener, true);
};
panel.addEventListener("load", loadListener);
createBrowser(viewNode, popupURI) {
let document = viewNode.ownerDocument;
return panel;
this.browser = document.createElementNS(XUL_NS, "browser");
this.browser.setAttribute("type", "content");
this.browser.setAttribute("disableglobalhistory", "true");
// Note: When using noautohide panels, the popup manager will add width and
// height attributes to the panel, breaking our resize code, if the browser
// starts out smaller than 30px by 10px. This isn't an issue now, but it
// will be if and when we popup debugging.
// This overrides the content's preferred size when displayed in a
// fixed-size, slide-in panel.
this.browser.setAttribute("flex", "1");
viewNode.appendChild(this.browser);
return new Promise(resolve => {
// The first load event is for about:blank.
// We can't finish setting up the browser until the binding has fully
// initialized. Waiting for the first load event guarantees that it has.
let loadListener = event => {
this.browser.removeEventListener("load", loadListener, true);
resolve();
};
this.browser.addEventListener("load", loadListener, true);
}).then(() => {
let { contentWindow } = this.browser;
contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.allowScriptsToClose();
this.context = new ExtensionPage(this.extension, {
type: "popup",
contentWindow,
uri: popupURI,
docShell: this.browser.docShell,
});
GlobalManager.injectInDocShell(this.browser.docShell, this.extension, this.context);
this.browser.setAttribute("src", this.context.uri.spec);
this.browser.addEventListener("load", this, true);
this.browser.addEventListener("DOMTitleChanged", this, true);
this.browser.addEventListener("DOMWindowClose", this, true);
});
}
// Resizes the browser to match the preferred size of the content.
resizeBrowser() {
let width, height;
try {
let w = {}, h = {};
this.browser.docShell.contentViewer.getContentSize(w, h);
width = w.value / this.window.devicePixelRatio;
height = h.value / this.window.devicePixelRatio;
// The width calculation is imperfect, and is often a fraction of a pixel
// too narrow, even after taking the ceiling, which causes lines of text
// to wrap.
width += 1;
} catch (e) {
// getContentSize can throw
[width, height] = [400, 400];
}
width = Math.ceil(Math.min(width, 800));
height = Math.ceil(Math.min(height, 600));
this.browser.style.width = `${width}px`;
this.browser.style.height = `${height}px`;
this._resolveContentReady();
}
}
global.PanelPopup = class PanelPopup extends BasePopup {
constructor(extension, imageNode, popupURL) {
let document = imageNode.ownerDocument;
let panel = document.createElement("panel");
panel.setAttribute("id", makeWidgetId(extension.id) + "-panel");
panel.setAttribute("class", "browser-extension-panel");
panel.setAttribute("type", "arrow");
panel.setAttribute("role", "group");
document.getElementById("mainPopupSet").appendChild(panel);
super(extension, panel, popupURL);
this.contentReady.then(() => {
panel.openPopup(imageNode, "bottomcenter topright", 0, 0, false, false);
});
}
get DESTROY_EVENT() {
return "popuphidden";
}
destroy() {
super.destroy();
this.viewNode.remove();
}
closePopup() {
this.viewNode.hidePopup();
}
};
global.ViewPopup = class ViewPopup extends BasePopup {
get DESTROY_EVENT() {
return "ViewHiding";
}
closePopup() {
CustomizableUI.hidePanelForNode(this.viewNode);
}
};
// Manages tab-specific context data, and dispatching tab select events

View File

@ -10,6 +10,9 @@
"XPCOMUtils": true,
"Task": true,
// Browser window globals.
"PanelUI": false,
// Test harness globals
"ExtensionTestUtils": false,

View File

@ -2,8 +2,141 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* testTabSwitchContext() {
function* runTests(options) {
function background(getTests) {
// Gets the current details of the browser action, and returns a
// promise that resolves to an object containing them.
function getDetails(tabId) {
return Promise.all([
new Promise(resolve => browser.browserAction.getTitle({tabId}, resolve)),
new Promise(resolve => browser.browserAction.getPopup({tabId}, resolve)),
new Promise(resolve => browser.browserAction.getBadgeText({tabId}, resolve)),
new Promise(resolve => browser.browserAction.getBadgeBackgroundColor({tabId}, resolve))]
).then(details => {
return Promise.resolve({ title: details[0],
popup: details[1],
badge: details[2],
badgeBackgroundColor: details[3] });
});
}
function checkDetails(expecting, tabId) {
return getDetails(tabId).then(details => {
browser.test.assertEq(expecting.title, details.title,
"expected value from getTitle");
browser.test.assertEq(expecting.popup, details.popup,
"expected value from getPopup");
browser.test.assertEq(expecting.badge, details.badge,
"expected value from getBadge");
browser.test.assertEq(String(expecting.badgeBackgroundColor),
String(details.badgeBackgroundColor),
"expected value from getBadgeBackgroundColor");
});
}
let expectDefaults = expecting => {
return checkDetails(expecting);
};
let tabs = [];
let tests = getTests(tabs, expectDefaults);
// Runs the next test in the `tests` array, checks the results,
// and passes control back to the outer test scope.
function nextTest() {
let test = tests.shift();
test(expecting => {
// Check that the API returns the expected values, and then
// run the next test.
new Promise(resolve => {
return browser.tabs.query({ active: true, currentWindow: true }, resolve);
}).then(tabs => {
return checkDetails(expecting, tabs[0].id);
}).then(() => {
// Check that the actual icon has the expected values, then
// run the next test.
browser.test.sendMessage("nextTest", expecting, tests.length);
});
});
}
browser.test.onMessage.addListener((msg) => {
if (msg != "runNextTest") {
browser.test.fail("Expecting 'runNextTest' message");
}
nextTest();
});
browser.tabs.query({ active: true, currentWindow: true }, resultTabs => {
tabs[0] = resultTabs[0].id;
nextTest();
});
}
let extension = ExtensionTestUtils.loadExtension({
manifest: options.manifest,
background: `(${background})(${options.getTests})`,
});
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
function checkDetails(details) {
let button = document.getElementById(browserActionId);
ok(button, "button exists");
let title = details.title || options.manifest.name;
is(button.getAttribute("image"), details.icon, "icon URL is correct");
is(button.getAttribute("tooltiptext"), title, "image title is correct");
is(button.getAttribute("label"), title, "image label is correct");
is(button.getAttribute("badge"), details.badge, "badge text is correct");
is(button.getAttribute("disabled") == "true", Boolean(details.disabled), "disabled state is correct");
if (details.badge && details.badgeBackgroundColor) {
let badge = button.ownerDocument.getAnonymousElementByAttribute(
button, "class", "toolbarbutton-badge");
let badgeColor = window.getComputedStyle(badge).backgroundColor;
let color = details.badgeBackgroundColor;
let expectedColor = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
is(badgeColor, expectedColor, "badge color is correct");
}
// TODO: Popup URL.
}
let awaitFinish = new Promise(resolve => {
extension.onMessage("nextTest", (expecting, testsRemaining) => {
checkDetails(expecting);
if (testsRemaining) {
extension.sendMessage("runNextTest");
} else {
resolve();
}
});
});
yield extension.startup();
yield awaitFinish;
yield extension.unload();
}
add_task(function* testTabSwitchContext() {
yield runTests({
manifest: {
"browser_action": {
"default_icon": "default.png",
@ -13,7 +146,7 @@ add_task(function* testTabSwitchContext() {
"permissions": ["tabs"],
},
background: function() {
getTests(tabs, expectDefaults) {
let details = [
{ "icon": browser.runtime.getURL("default.png"),
"popup": browser.runtime.getURL("default.html"),
@ -50,10 +183,7 @@ add_task(function* testTabSwitchContext() {
"badgeBackgroundColor": [0, 0xff, 0, 0xff] },
];
let tabs = [];
let expectDefaults;
let tests = [
return [
expect => {
browser.test.log("Initial state, expect default properties.");
expectDefaults(details[0]).then(() => {
@ -157,124 +287,82 @@ add_task(function* testTabSwitchContext() {
});
},
];
// Gets the current details of the browser action, and returns a
// promise that resolves to an object containing them.
function getDetails(tabId) {
return Promise.all([
new Promise(resolve => browser.browserAction.getTitle({tabId}, resolve)),
new Promise(resolve => browser.browserAction.getPopup({tabId}, resolve)),
new Promise(resolve => browser.browserAction.getBadgeText({tabId}, resolve)),
new Promise(resolve => browser.browserAction.getBadgeBackgroundColor({tabId}, resolve))]
).then(details => {
return Promise.resolve({ title: details[0],
popup: details[1],
badge: details[2],
badgeBackgroundColor: details[3] });
});
}
function checkDetails(expecting, tabId) {
return getDetails(tabId).then(details => {
browser.test.assertEq(expecting.title, details.title,
"expected value from getTitle");
browser.test.assertEq(expecting.popup, details.popup,
"expected value from getPopup");
browser.test.assertEq(expecting.badge, details.badge,
"expected value from getBadge");
browser.test.assertEq(String(expecting.badgeBackgroundColor),
String(details.badgeBackgroundColor),
"expected value from getBadgeBackgroundColor");
});
}
expectDefaults = expecting => {
return checkDetails(expecting);
};
// Runs the next test in the `tests` array, checks the results,
// and passes control back to the outer test scope.
function nextTest() {
let test = tests.shift();
test(expecting => {
// Check that the API returns the expected values, and then
// run the next test.
new Promise(resolve => {
return browser.tabs.query({ active: true, currentWindow: true }, resolve);
}).then(tabs => {
return checkDetails(expecting, tabs[0].id);
}).then(() => {
// Check that the actual icon has the expected values, then
// run the next test.
browser.test.sendMessage("nextTest", expecting, tests.length);
});
});
}
browser.test.onMessage.addListener((msg) => {
if (msg != "runNextTest") {
browser.test.fail("Expecting 'runNextTest' message");
}
nextTest();
});
browser.tabs.query({ active: true, currentWindow: true }, resultTabs => {
tabs[0] = resultTabs[0].id;
nextTest();
});
},
});
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
function checkDetails(details) {
let button = document.getElementById(browserActionId);
ok(button, "button exists");
is(button.getAttribute("image"), details.icon, "icon URL is correct");
is(button.getAttribute("tooltiptext"), details.title, "image title is correct");
is(button.getAttribute("label"), details.title, "image label is correct");
is(button.getAttribute("aria-label"), details.title, "image aria-label is correct");
is(button.getAttribute("badge"), details.badge, "badge text is correct");
is(button.getAttribute("disabled") == "true", Boolean(details.disabled), "disabled state is correct");
if (details.badge && details.badgeBackgroundColor) {
let badge = button.ownerDocument.getAnonymousElementByAttribute(
button, "class", "toolbarbutton-badge");
let badgeColor = window.getComputedStyle(badge).backgroundColor;
let color = details.badgeBackgroundColor;
let expectedColor = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
is(badgeColor, expectedColor, "badge color is correct");
}
// TODO: Popup URL.
}
let awaitFinish = new Promise(resolve => {
extension.onMessage("nextTest", (expecting, testsRemaining) => {
checkDetails(expecting);
if (testsRemaining) {
extension.sendMessage("runNextTest");
} else {
resolve();
}
});
});
yield extension.startup();
yield awaitFinish;
yield extension.unload();
});
add_task(function* testDefaultTitle() {
yield runTests({
manifest: {
"name": "Foo Extension",
"browser_action": {
"default_icon": "icon.png",
},
"permissions": ["tabs"],
},
getTests(tabs, expectDefaults) {
let details = [
{ "title": "Foo Extension",
"popup": "",
"badge": "",
"badgeBackgroundColor": null,
"icon": browser.runtime.getURL("icon.png") },
{ "title": "Foo Title",
"popup": "",
"badge": "",
"badgeBackgroundColor": null,
"icon": browser.runtime.getURL("icon.png") },
{ "title": "Bar Title",
"popup": "",
"badge": "",
"badgeBackgroundColor": null,
"icon": browser.runtime.getURL("icon.png") },
{ "title": "",
"popup": "",
"badge": "",
"badgeBackgroundColor": null,
"icon": browser.runtime.getURL("icon.png") },
];
return [
expect => {
browser.test.log("Initial state. Expect extension title as default title.");
expectDefaults(details[0]).then(() => {
expect(details[0]);
});
},
expect => {
browser.test.log("Change the title. Expect new title.");
browser.browserAction.setTitle({ tabId: tabs[0], title: "Foo Title" });
expectDefaults(details[0]).then(() => {
expect(details[1]);
});
},
expect => {
browser.test.log("Change the default. Expect same properties.");
browser.browserAction.setTitle({ title: "Bar Title" });
expectDefaults(details[2]).then(() => {
expect(details[1]);
});
},
expect => {
browser.test.log("Clear the title. Expect new default title.");
browser.browserAction.setTitle({ tabId: tabs[0], title: "" });
expectDefaults(details[2]).then(() => {
expect(details[2]);
});
},
expect => {
browser.test.log("Set default title to null string. Expect null string from API, extension title in UI.");
browser.browserAction.setTitle({ title: "" });
expectDefaults(details[3]).then(() => {
expect(details[3]);
});
},
];
},
});
});

View File

@ -2,21 +2,7 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
function promisePopupShown(popup) {
return new Promise(resolve => {
if (popup.popupOpen) {
resolve();
} else {
let onPopupShown = event => {
popup.removeEventListener("popupshown", onPopupShown);
resolve();
};
popup.addEventListener("popupshown", onPopupShown);
}
});
}
add_task(function* testPageActionPopup() {
function* testInArea(area) {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"background": {
@ -116,30 +102,34 @@ add_task(function* testPageActionPopup() {
},
});
let panelId = makeWidgetId(extension.id) + "-panel";
extension.onMessage("send-click", () => {
clickBrowserAction(extension);
});
let widget;
extension.onMessage("next-test", Task.async(function* () {
let panel = document.getElementById(panelId);
if (panel) {
yield promisePopupShown(panel);
panel.hidePopup();
panel = document.getElementById(panelId);
is(panel, null, "panel successfully removed from document after hiding");
if (!widget) {
widget = getBrowserActionWidget(extension);
CustomizableUI.addWidgetToArea(widget.id, area);
}
yield closeBrowserAction(extension);
extension.sendMessage("next-test");
}));
yield Promise.all([extension.startup(), extension.awaitFinish("browseraction-tests-done")]);
yield extension.unload();
let panel = document.getElementById(panelId);
is(panel, null, "browserAction panel removed from document");
let view = document.getElementById(widget.viewId);
is(view, null, "browserAction view removed from document");
}
add_task(function* testBrowserActionInToolbar() {
yield testInArea(CustomizableUI.AREA_NAVBAR);
});
add_task(function* testBrowserActionInPanel() {
yield testInArea(CustomizableUI.AREA_PANEL);
});

View File

@ -33,23 +33,13 @@ add_task(function* () {
yield extension.startup();
let widgetId = makeWidgetId(extension.id) + "-browser-action";
let node = CustomizableUI.getWidget(widgetId).forWindow(window).node;
// Do this a few times to make sure the pop-up is reloaded each time.
for (let i = 0; i < 3; i++) {
let evt = new CustomEvent("command", {
bubbles: true,
cancelable: true,
});
node.dispatchEvent(evt);
clickBrowserAction(extension);
yield extension.awaitMessage("popup");
let panel = node.querySelector("panel");
if (panel) {
panel.hidePopup();
}
closeBrowserAction(extension);
}
yield extension.unload();

View File

@ -110,23 +110,13 @@ add_task(function* () {
yield checkWindow("background", winId2, "win2");
function* triggerPopup(win, callback) {
let widgetId = makeWidgetId(extension.id) + "-browser-action";
let node = CustomizableUI.getWidget(widgetId).forWindow(win).node;
let evt = new CustomEvent("command", {
bubbles: true,
cancelable: true,
});
node.dispatchEvent(evt);
yield clickBrowserAction(extension, win);
yield extension.awaitMessage("popup-ready");
yield callback();
let panel = node.querySelector("panel");
if (panel) {
panel.hidePopup();
}
closeBrowserAction(extension, win);
}
// Set focus to some other window.

View File

@ -116,25 +116,21 @@ add_task(function* () {
yield checkViews("background", 2, 0);
function* triggerPopup(win, callback) {
let widgetId = makeWidgetId(extension.id) + "-browser-action";
let node = CustomizableUI.getWidget(widgetId).forWindow(win).node;
let evt = new CustomEvent("command", {
bubbles: true,
cancelable: true,
});
node.dispatchEvent(evt);
yield clickBrowserAction(extension, win);
yield extension.awaitMessage("popup-ready");
yield callback();
let panel = node.querySelector("panel");
if (panel) {
panel.hidePopup();
}
closeBrowserAction(extension, win);
}
// The popup occasionally closes prematurely if we open it immediately here.
// I'm not sure what causes it to close (it's something internal, and seems to
// be focus-related, but it's not caused by JS calling hidePopup), but even a
// short timeout seems to consistently fix it.
yield new Promise(resolve => win1.setTimeout(resolve, 10));
yield triggerPopup(win1, function*() {
yield checkViews("background", 2, 1);
yield checkViews("popup", 2, 1);

View File

@ -2,21 +2,9 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
function promisePopupShown(popup) {
return new Promise(resolve => {
if (popup.popupOpen) {
resolve();
} else {
let onPopupShown = event => {
popup.removeEventListener("popupshown", onPopupShown);
resolve();
};
popup.addEventListener("popupshown", onPopupShown);
}
});
}
add_task(function* testPageActionPopup() {
let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}"></script></head></html>`;
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"background": {
@ -28,17 +16,17 @@ add_task(function* testPageActionPopup() {
},
files: {
"popup-a.html": `<script src="popup-a.js"></script>`,
"popup-a.html": scriptPage("popup-a.js"),
"popup-a.js": function() {
browser.runtime.sendMessage("from-popup-a");
},
"data/popup-b.html": `<script src="popup-b.js"></script>`,
"data/popup-b.html": scriptPage("popup-b.js"),
"data/popup-b.js": function() {
browser.runtime.sendMessage("from-popup-b");
},
"data/background.html": `<script src="background.js"></script>`,
"data/background.html": scriptPage("background.js"),
"data/background.js": function() {
let tabId;

View File

@ -42,19 +42,6 @@ add_task(function* testPageActionPopup() {
},
});
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
let pageActionId = makeWidgetId(extension.id) + "-page-action";
function openPopup(buttonId) {
let button = document.getElementById(buttonId);
if (buttonId == pageActionId) {
// TODO: I don't know why a proper synthesized event doesn't work here.
button.dispatchEvent(new MouseEvent("click", {}));
} else {
EventUtils.synthesizeMouseAtCenter(button, {}, window);
}
}
let promiseConsoleMessage = pattern => new Promise(resolve => {
Services.console.registerListener(function listener(msg) {
if (pattern.test(msg.message)) {
@ -72,21 +59,25 @@ add_task(function* testPageActionPopup() {
// BrowserAction:
let awaitMessage = promiseConsoleMessage(/WebExt Privilege Escalation: BrowserAction/);
SimpleTest.expectUncaughtException();
openPopup(browserActionId);
yield clickBrowserAction(extension);
let message = yield awaitMessage;
ok(message.includes("WebExt Privilege Escalation: BrowserAction: typeof(browser) = undefined"),
`No BrowserAction API injection`);
yield closeBrowserAction(extension);
// PageAction
awaitMessage = promiseConsoleMessage(/WebExt Privilege Escalation: PageAction/);
SimpleTest.expectUncaughtException();
openPopup(pageActionId);
yield clickPageAction(extension);
message = yield awaitMessage;
ok(message.includes("WebExt Privilege Escalation: PageAction: typeof(browser) = undefined"),
`No PageAction API injection: ${message}`);
yield closePageAction(extension);
SimpleTest.expectUncaughtException(false);
@ -95,12 +86,13 @@ add_task(function* testPageActionPopup() {
yield extension.awaitMessage("ok");
// Check that unprivileged documents don't get the API.
openPopup(browserActionId);
yield clickBrowserAction(extension);
yield extension.awaitMessage("from-popup-a");
yield closeBrowserAction(extension);
openPopup(pageActionId);
yield clickPageAction(extension);
yield extension.awaitMessage("from-popup-b");
yield closePageAction(extension);
yield extension.unload();
});

View File

@ -48,6 +48,11 @@ function* testHasPermission(params) {
extension.sendMessage("execute-script");
yield extension.awaitFinish("executeScript");
if (params.tearDown) {
yield params.tearDown(extension);
}
yield extension.unload();
}
@ -82,6 +87,7 @@ add_task(function* testGoodPermissions() {
return Promise.resolve();
},
setup: clickBrowserAction,
tearDown: closeBrowserAction,
});
info("Test activeTab permission with a page action click");
@ -99,6 +105,7 @@ add_task(function* testGoodPermissions() {
});
},
setup: clickPageAction,
tearDown: closePageAction,
});
info("Test activeTab permission with a browser action w/popup click");
@ -108,6 +115,7 @@ add_task(function* testGoodPermissions() {
"browser_action": { "default_popup": "_blank.html" },
},
setup: clickBrowserAction,
tearDown: closeBrowserAction,
});
info("Test activeTab permission with a page action w/popup click");
@ -125,6 +133,7 @@ add_task(function* testGoodPermissions() {
});
},
setup: clickPageAction,
tearDown: closePageAction,
});
info("Test activeTab permission with a context menu click");

View File

@ -63,6 +63,7 @@ add_task(function* () {
clickBrowserAction(extension);
yield extension.awaitMessage("popup-finished");
yield closeBrowserAction(extension);
yield extension.unload();
});

View File

@ -2,7 +2,13 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
/* exported AppConstants CustomizableUI forceGC makeWidgetId focusWindow clickBrowserAction clickPageAction */
/* exported CustomizableUI makeWidgetId focusWindow forceGC
* getBrowserActionWidget
* clickBrowserAction clickPageAction
* getBrowserActionPopup getPageActionPopup
* closeBrowserAction closePageAction
* promisePopupShown
*/
var {AppConstants} = Cu.import("resource://gre/modules/AppConstants.jsm");
var {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm");
@ -39,12 +45,58 @@ var focusWindow = Task.async(function* focusWindow(win) {
yield promise;
});
function clickBrowserAction(extension, win = window) {
let browserActionId = makeWidgetId(extension.id) + "-browser-action";
let elem = win.document.getElementById(browserActionId);
function promisePopupShown(popup) {
return new Promise(resolve => {
if (popup.state == "open") {
resolve();
} else {
let onPopupShown = event => {
popup.removeEventListener("popupshown", onPopupShown);
resolve();
};
popup.addEventListener("popupshown", onPopupShown);
}
});
}
EventUtils.synthesizeMouseAtCenter(elem, {}, win);
return new Promise(SimpleTest.executeSoon);
function getBrowserActionWidget(extension) {
return CustomizableUI.getWidget(makeWidgetId(extension.id) + "-browser-action");
}
function getBrowserActionPopup(extension, win = window) {
let group = getBrowserActionWidget(extension);
if (group.areaType == CustomizableUI.TYPE_TOOLBAR) {
return win.document.getElementById("customizationui-widget-panel");
}
return null;
}
var clickBrowserAction = Task.async(function* (extension, win = window) {
let group = getBrowserActionWidget(extension);
let widget = group.forWindow(win);
if (group.areaType == CustomizableUI.TYPE_TOOLBAR) {
ok(!widget.overflowed, "Expect widget not to be overflowed");
} else if (group.areaType == CustomizableUI.TYPE_MENU_PANEL) {
yield win.PanelUI.show();
}
EventUtils.synthesizeMouseAtCenter(widget.node, {}, win);
});
function closeBrowserAction(extension, win = window) {
let group = getBrowserActionWidget(extension);
let node = win.document.getElementById(group.viewId);
CustomizableUI.hidePanelForNode(node);
return Promise.resolve();
}
function getPageActionPopup(extension, win = window) {
let panelId = makeWidgetId(extension.id) + "-panel";
return win.document.getElementById(panelId);
}
function clickPageAction(extension, win = window) {
@ -63,3 +115,13 @@ function clickPageAction(extension, win = window) {
EventUtils.synthesizeMouseAtCenter(elem, {}, win);
return new Promise(SimpleTest.executeSoon);
}
function closePageAction(extension, win = window) {
let node = getPageActionPopup(extension, win);
if (node) {
node.hidePopup();
}
return Promise.resolve();
}

View File

@ -188,20 +188,23 @@ PlacesController.prototype = {
!PlacesUtils.asQuery(this._view.result.root).queryOptions.excludeItems &&
this._view.result.sortingMode ==
Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
case "placesCmd_show:info":
var selectedNode = this._view.selectedNode;
case "placesCmd_show:info": {
let selectedNode = this._view.selectedNode;
return selectedNode && PlacesUtils.getConcreteItemId(selectedNode) != -1
case "placesCmd_reload":
}
case "placesCmd_reload": {
// Livemark containers
var selectedNode = this._view.selectedNode;
let selectedNode = this._view.selectedNode;
return selectedNode && this.hasCachedLivemarkInfo(selectedNode);
case "placesCmd_sortBy:name":
var selectedNode = this._view.selectedNode;
}
case "placesCmd_sortBy:name": {
let selectedNode = this._view.selectedNode;
return selectedNode &&
PlacesUtils.nodeIsFolder(selectedNode) &&
!PlacesUIUtils.isContentsReadOnly(selectedNode) &&
this._view.result.sortingMode ==
Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
}
case "placesCmd_createBookmark":
var node = this._view.selectedNode;
return node && PlacesUtils.nodeIsURI(node) && node.itemId == -1;
@ -963,16 +966,13 @@ PlacesController.prototype = {
}
// Do removal in chunks to give some breath to main-thread.
function pagesChunkGenerator(aURIs) {
function* pagesChunkGenerator(aURIs) {
while (aURIs.length) {
let URIslice = aURIs.splice(0, REMOVE_PAGES_CHUNKLEN);
PlacesUtils.bhistory.removePages(URIslice, URIslice.length);
Services.tm.mainThread.dispatch(function() {
try {
gen.next();
} catch (ex if ex instanceof StopIteration) {}
}, Ci.nsIThread.DISPATCH_NORMAL);
yield undefined;
Services.tm.mainThread.dispatch(() => gen.next(),
Ci.nsIThread.DISPATCH_NORMAL);
yield unefined;
}
}
let gen = pagesChunkGenerator(URIs);

View File

@ -366,7 +366,7 @@ var gEditItemOverlay = {
* set. Then we sort it descendingly based on the time field.
*/
this._recentFolders = [];
for (var i = 0; i < folderIds.length; i++) {
for (let i = 0; i < folderIds.length; i++) {
var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO);
this._recentFolders.push({ folderId: folderIds[i], lastUsed: lastUsed });
}
@ -380,7 +380,7 @@ var gEditItemOverlay = {
var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST,
this._recentFolders.length);
for (var i = 0; i < numberOfItems; i++) {
for (let i = 0; i < numberOfItems; i++) {
this._appendFolderItemToMenupopup(menupopup,
this._recentFolders[i].folderId);
}

View File

@ -418,7 +418,7 @@ var PlacesOrganizer = {
while (restorePopup.childNodes.length > 1)
restorePopup.removeChild(restorePopup.firstChild);
Task.spawn(function() {
Task.spawn(function* () {
let backupFiles = yield PlacesBackups.getBackupFiles();
if (backupFiles.length == 0)
return;
@ -465,18 +465,16 @@ var PlacesOrganizer = {
/**
* Called when a menuitem is selected from the restore menu.
*/
onRestoreMenuItemClick: function PO_onRestoreMenuItemClick(aMenuItem) {
Task.spawn(function() {
let backupName = aMenuItem.getAttribute("value");
let backupFilePaths = yield PlacesBackups.getBackupFiles();
for (let backupFilePath of backupFilePaths) {
if (OS.Path.basename(backupFilePath) == backupName) {
PlacesOrganizer.restoreBookmarksFromFile(backupFilePath);
break;
}
onRestoreMenuItemClick: Task.async(function* (aMenuItem) {
let backupName = aMenuItem.getAttribute("value");
let backupFilePaths = yield PlacesBackups.getBackupFiles();
for (let backupFilePath of backupFilePaths) {
if (OS.Path.basename(backupFilePath) == backupName) {
PlacesOrganizer.restoreBookmarksFromFile(backupFilePath);
break;
}
});
},
}
}),
/**
* Called when 'Choose File...' is selected from the restore menu.
@ -521,7 +519,7 @@ var PlacesOrganizer = {
PlacesUIUtils.getString("bookmarksRestoreAlert")))
return;
Task.spawn(function() {
Task.spawn(function* () {
try {
yield BookmarkJSONUtils.importFromFile(aFilePath, true);
} catch(ex) {
@ -633,7 +631,7 @@ var PlacesOrganizer = {
// don't update the panel if we are already editing this node unless we're
// in multi-edit mode
if (selectedNode) {
var concreteId = PlacesUtils.getConcreteItemId(selectedNode);
let concreteId = PlacesUtils.getConcreteItemId(selectedNode);
var nodeIsSame = gEditItemOverlay.itemId == selectedNode.itemId ||
gEditItemOverlay.itemId == concreteId ||
(selectedNode.itemId == -1 && gEditItemOverlay.uri &&
@ -653,7 +651,7 @@ var PlacesOrganizer = {
// does allow setting properties for folder shortcuts as well, but since
// the UI does not distinct between the couple, we better just show
// the concrete item properties for shortcuts to root nodes.
var concreteId = PlacesUtils.getConcreteItemId(selectedNode);
let concreteId = PlacesUtils.getConcreteItemId(selectedNode);
var isRootItem = concreteId != -1 && PlacesUtils.isRootItem(concreteId);
var readOnly = isRootItem ||
selectedNode.parent.itemId == PlacesUIUtils.leftPaneFolderId;
@ -1206,13 +1204,13 @@ var ViewMenu = {
if (aColumn) {
columnId = aColumn.getAttribute("anonid");
if (!aDirection) {
var sortColumn = this._getSortColumn();
let sortColumn = this._getSortColumn();
if (sortColumn)
aDirection = sortColumn.getAttribute("sortDirection");
}
}
else {
var sortColumn = this._getSortColumn();
let sortColumn = this._getSortColumn();
columnId = sortColumn ? sortColumn.getAttribute("anonid") : "title";
}

View File

@ -239,7 +239,7 @@
// Walk the list backwards (opening from the root of the hierarchy)
// opening each folder as we go.
for (var i = parents.length - 1; i >= 0; --i) {
var index = view.treeIndexForNode(parents[i]);
let index = view.treeIndexForNode(parents[i]);
if (index != Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE &&
view.isContainer(index) && !view.isContainerOpen(index))
view.toggleOpenState(index);
@ -247,7 +247,7 @@
// Select the specified node...
}
var index = view.treeIndexForNode(node);
let index = view.treeIndexForNode(node);
if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE)
return;
@ -659,10 +659,10 @@
selection.selectEventsSuppressed = true;
selection.clearSelection();
// Open nodes containing found items
for (var i = 0; i < nodesToOpen.length; i++) {
for (let i = 0; i < nodesToOpen.length; i++) {
nodesToOpen[i].containerOpen = true;
}
for (var i = 0; i < nodes.length; i++) {
for (let i = 0; i < nodes.length; i++) {
var index = resultview.treeIndexForNode(nodes[i]);
if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE)
continue;

View File

@ -7,7 +7,7 @@
const SHORTCUT_URL = "place:folder=2";
const QUERY_URL = "place:sort=8&maxResults=10";
add_task(function copy_toolbar_shortcut() {
add_task(function* copy_toolbar_shortcut() {
let library = yield promiseLibrary();
registerCleanupFunction(function () {
@ -35,7 +35,7 @@ add_task(function copy_toolbar_shortcut() {
"original is still a folder shortcut");
});
add_task(function copy_history_query() {
add_task(function* copy_history_query() {
let library = yield promiseLibrary();
library.PlacesOrganizer.selectLeftPaneQuery("History");

View File

@ -13,7 +13,7 @@ function makeBookmarkFor(url, keyword) {
}
add_task(function openKeywordBookmarkWithWindowOpen() {
add_task(function* openKeywordBookmarkWithWindowOpen() {
// This is the current default, but let's not assume that...
yield new Promise((resolve, reject) => {
SpecialPowers.pushPrefEnv({ 'set': [[ 'browser.link.open_newwindow', 3 ],

View File

@ -97,7 +97,7 @@ gTests.push({
checkAddInfoFieldsNotCollapsed(PO);
// open first bookmark
var view = ContentTree.view.view;
view = ContentTree.view.view;
ok(view.rowCount > 0, "Bookmark item exists.");
view.selection.select(0);
checkInfoBoxSelected(PO);

View File

@ -5,7 +5,7 @@ var contextMenu = document.getElementById("placesContext");
var newBookmarkItem = document.getElementById("placesContext_new:bookmark");
waitForExplicitFinish();
add_task(function testPopup() {
add_task(function* testPopup() {
info("Checking popup context menu before moving the bookmarks button");
yield checkPopupContextMenu();
let pos = CustomizableUI.getPlacementOfWidget("bookmarks-menu-button").position;

View File

@ -34,3 +34,8 @@ tampering with your connection.</b>">
you know there's a good reason why this site doesn't use trusted identification.">
<!ENTITY certerror.addException.label "Add Exception…">
<!ENTITY certerror.copyToClipboard.label "Copy text to clipboard">
<!ENTITY errorReporting.automatic "Report errors like this to help Mozilla identify misconfigured sites">
<!ENTITY errorReporting.sending "Sending report">
<!ENTITY errorReporting.sent "Report sent">
<!ENTITY errorReporting.tryAgain "Try again">

View File

@ -208,7 +208,6 @@ functionality specific to firefox. -->
<!ENTITY errorReporting.learnMore "Learn more…">
<!ENTITY errorReporting.sending "Sending report">
<!ENTITY errorReporting.sent "Report sent">
<!ENTITY errorReporting.report "Report">
<!ENTITY errorReporting.tryAgain "Try again">
<!ENTITY remoteXUL.title "Remote XUL">

View File

@ -96,6 +96,7 @@ body {
/* Advanced section is hidden via inline styles until the link is clicked */
#advancedPanel {
position: absolute;
background-color: white;
color: var(--in-content-text-color);
border: 1px lightgray solid;
@ -110,3 +111,10 @@ body {
.hostname {
font-weight: bold;
}
#reportCertificateErrorRetry,
#certificateErrorReporting,
#reportSendingMessage,
#reportSentMessage {
display: none;
}

View File

@ -116,7 +116,6 @@ button:disabled {
}
#certificateErrorReporting,
#reportCertificateError,
#reportSentMessage {
display: none;
}

View File

@ -250,6 +250,11 @@ panelmultiview[nosubviews=true] > .panel-viewcontainer > .panel-viewstack > .pan
max-width: @standaloneSubviewWidth@;
}
/* Give WebExtension stand-alone panels extra width for Chrome compatibility */
.cui-widget-panel[viewId^=PanelUI-webext-] .panel-mainview {
max-width: 800px;
}
panelview:not([mainview]) .toolbarbutton-text,
.cui-widget-panel toolbarbutton > .toolbarbutton-text {
text-align: start;

View File

@ -23,7 +23,6 @@
// devtools coding style.
// Rules from the mozilla plugin
"mozilla/balanced-listeners": 2,
"mozilla/mark-test-function-used": 1,
"mozilla/no-aArgs": 1,
"mozilla/no-cpows-in-tests": 1,

View File

@ -36,6 +36,7 @@ Cu.import("resource://devtools/shared/gcli/Templater.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
loader.lazyRequireGetter(this, "CSS", "CSS");
loader.lazyGetter(this, "DOMParser", function() {
return Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser);
});
@ -2684,7 +2685,7 @@ ElementEditor.prototype = {
*/
getAttributeElement: function(attrName) {
return this.attrList.querySelector(
".attreditor[data-attr=" + attrName + "] .attr-value");
".attreditor[data-attr=" + CSS.escape(attrName) + "] .attr-value");
},
/**

View File

@ -43,6 +43,12 @@ const TEST_DATA = [{
mutate: (doc, rootNode) => {
rootNode.setAttribute("test-name", "value-" + Date.now());
}
}, {
desc: "Adding an attribute with css reserved characters should flash the attribute",
attribute: "one:two",
mutate: (doc, rootNode) => {
rootNode.setAttribute("one:two", "value-" + Date.now());
}
}, {
desc: "Editing an attribute should flash the attribute",
attribute: "class",

View File

@ -6,7 +6,7 @@
define(function(require, exports, module) {
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const { createFactories } = require("./reps/rep-utils");
const { Headers } = createFactories(require("./headers"));
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));

View File

@ -6,7 +6,7 @@
define(function(require, exports, module) {
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
// Constants
const DOM = React.DOM;

View File

@ -6,7 +6,7 @@
define(function(require, exports, module) {
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const { createFactories } = require("./reps/rep-utils");
const { TreeView } = createFactories(require("./reps/tree-view"));
const { SearchBox } = createFactories(require("./search-box"));

View File

@ -8,7 +8,7 @@
define(function(require, exports, module) {
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const { createFactories } = require("./reps/rep-utils");
const { JsonPanel } = createFactories(require("./json-panel"));
const { TextPanel } = createFactories(require("./text-panel"));

View File

@ -9,7 +9,7 @@
define(function(require, exports, module) {
// Dependencies
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const { createFactories } = require("./rep-utils");
const { Rep } = createFactories(require("./rep"));
const { ObjectBox } = createFactories(require("./object-box"));

View File

@ -9,7 +9,7 @@
define(function(require, exports, module) {
// Dependencies
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const DOM = React.DOM;
/**

View File

@ -9,7 +9,7 @@
define(function(require, exports, module) {
// Dependencies
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const { createFactories } = require("./rep-utils");
const { ObjectBox } = createFactories(require("./object-box"));

View File

@ -9,7 +9,7 @@
define(function(require, exports, module) {
// Dependencies
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const { createFactories } = require("./rep-utils");
const { ObjectBox } = createFactories(require("./object-box"));

View File

@ -9,7 +9,7 @@
define(function(require, exports, module) {
// Dependencies
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const DOM = React.DOM;
/**

View File

@ -9,7 +9,7 @@
define(function(require, exports, module) {
// Dependencies
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const DOM = React.DOM;
/**

View File

@ -9,7 +9,7 @@
define(function(require, exports, module) {
// Dependencies
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const { createFactories } = require("./rep-utils");
const { ObjectBox } = createFactories(require("./object-box"));
const { Caption } = createFactories(require("./caption"));

View File

@ -9,7 +9,7 @@
define(function(require, exports, module) {
// Dependencies
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
/**
* Create React factories for given arguments.

View File

@ -9,7 +9,7 @@
define(function(require, exports, module) {
// Dependencies
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
// Load all existing rep templates
const { Undefined } = require("./undefined");

View File

@ -9,7 +9,7 @@
define(function(require, exports, module) {
// Dependencies
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const { createFactories } = require("./rep-utils");
const { ObjectBox } = createFactories(require("./object-box"));

View File

@ -8,7 +8,7 @@
define(function(require, exports, module) {
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const DOM = React.DOM;
/**

View File

@ -8,7 +8,7 @@
define(function(require, exports, module) {
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const DOM = React.DOM;
/**

View File

@ -7,7 +7,7 @@
define(function(require, exports, module) {
// Dependencies
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const { createFactories } = require("./rep-utils");
const { Rep } = createFactories(require("./rep"));
const { StringRep } = require("./string");

View File

@ -9,7 +9,7 @@
define(function(require, exports, module) {
// Dependencies
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const { createFactories } = require("./rep-utils");
const { ObjectBox } = createFactories(require("./object-box"));

View File

@ -8,7 +8,7 @@
define(function(require, exports, module) {
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const DOM = React.DOM;

View File

@ -6,7 +6,7 @@
define(function(require, exports, module) {
const React = require("react");
const React = require("devtools/client/shared/vendor/react");
const { createFactories } = require("./reps/rep-utils");
const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
const DOM = React.DOM;

View File

@ -7,9 +7,7 @@
define(function(require, exports, module) {
// ReactJS
const ReactDOM = require("react-dom");
// RDP Inspector
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const { createFactories } = require("./components/reps/rep-utils");
const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));

View File

@ -16,15 +16,19 @@
*
* The path mapping uses paths fallback (a feature supported by RequireJS)
* See also: http://requirejs.org/docs/api.html#pathsfallbacks
*
* React module ID is using exactly the same (relative) path as the rest
* of the code base, so it's consistent and modules can be easily reused.
*/
require.config({
baseUrl: ".",
paths: {
"react": [
"devtools/client/shared/vendor/react": [
"resource://devtools/client/shared/vendor/react-dev",
"resource://devtools/client/shared/vendor/react"
],
"react-dom": "resource://devtools/client/shared/vendor/react-dom"
"devtools/client/shared/vendor/react-dom":
"resource://devtools/client/shared/vendor/react-dom"
}
});

View File

@ -4,7 +4,7 @@
const { isSavedFrame } = require("devtools/shared/DevToolsUtils");
const { DOM: dom, createClass, createFactory } = require("devtools/client/shared/vendor/react");
const { L10N } = require("../utils");
const { L10N, formatNumber, formatPercent } = require("../utils");
const Frame = createFactory(require("devtools/client/shared/components/frame"));
const unknownSourceString = L10N.getStr("unknownSource");
const { TREE_ROW_HEIGHT } = require("../constants");
@ -12,25 +12,6 @@ const { TREE_ROW_HEIGHT } = require("../constants");
const CensusTreeItem = module.exports = createClass({
displayName: "CensusTreeItem",
formatPercent(showSign, percent) {
return L10N.getFormatStr("tree-item.percent",
this.formatNumber(showSign, percent));
},
formatNumber(showSign, number) {
const rounded = Math.round(number);
if (rounded === 0 || rounded === -0) {
return "0";
}
let sign = "";
if (showSign) {
sign = rounded < 0 ? "-" : "+";
}
return sign + Math.abs(rounded);
},
shouldComponentUpdate(nextProps, nextState) {
return this.props.item != nextProps.item
|| this.props.depth != nextProps.depth
@ -52,17 +33,17 @@ const CensusTreeItem = module.exports = createClass({
onViewSourceInDebugger,
} = this.props;
const bytes = this.formatNumber(showSign, item.bytes);
const percentBytes = this.formatPercent(showSign, getPercentBytes(item.bytes));
const bytes = formatNumber(item.bytes, showSign);
const percentBytes = formatPercent(getPercentBytes(item.bytes), showSign);
const count = this.formatNumber(showSign, item.count);
const percentCount = this.formatPercent(showSign, getPercentCount(item.count));
const count = formatNumber(item.count, showSign);
const percentCount = formatPercent(getPercentCount(item.count), showSign);
const totalBytes = this.formatNumber(showSign, item.totalBytes);
const percentTotalBytes = this.formatPercent(showSign, getPercentBytes(item.totalBytes));
const totalBytes = formatNumber(item.totalBytes, showSign);
const percentTotalBytes = formatPercent(getPercentBytes(item.totalBytes), showSign);
const totalCount = this.formatNumber(showSign, item.totalCount);
const percentTotalCount = this.formatPercent(showSign, getPercentCount(item.totalCount));
const totalCount = formatNumber(item.totalCount, showSign);
const percentTotalCount = formatPercent(getPercentCount(item.totalCount), showSign);
return dom.div({ className: `heap-tree-item ${focused ? "focused" :""}` },
dom.span({ className: "heap-tree-item-field heap-tree-item-bytes" },

View File

@ -4,7 +4,7 @@
const { assert, isSavedFrame } = require("devtools/shared/DevToolsUtils");
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
const { L10N } = require("../utils");
const { L10N, formatNumber, formatPercent } = require("../utils");
const Frame = createFactory(require("devtools/client/shared/components/frame"));
const { TREE_ROW_HEIGHT } = require("../constants");
@ -28,20 +28,6 @@ const DominatorTreeItem = module.exports = createClass({
onViewSourceInDebugger: PropTypes.func.isRequired,
},
formatPercent(percent) {
return L10N.getFormatStr("tree-item.percent",
this.formatNumber(percent));
},
formatNumber(number) {
const rounded = Math.round(number);
if (rounded === 0 || rounded === -0) {
return "0";
}
return String(Math.abs(rounded));
},
shouldComponentUpdate(nextProps, nextState) {
return this.props.item != nextProps.item
|| this.props.depth != nextProps.depth
@ -58,11 +44,11 @@ const DominatorTreeItem = module.exports = createClass({
onViewSourceInDebugger,
} = this.props;
const retainedSize = this.formatNumber(item.retainedSize);
const percentRetainedSize = this.formatPercent(getPercentSize(item.retainedSize));
const retainedSize = formatNumber(item.retainedSize);
const percentRetainedSize = formatPercent(getPercentSize(item.retainedSize));
const shallowSize = this.formatNumber(item.shallowSize);
const percentShallowSize = this.formatPercent(getPercentSize(item.shallowSize));
const shallowSize = formatNumber(item.shallowSize);
const percentShallowSize = formatPercent(getPercentSize(item.shallowSize));
// Build up our label UI as an array of each label piece, which is either a
// string or a frame, and separators in between them.

View File

@ -3,7 +3,8 @@
/**
* Tests the task creator `takeSnapshotAndCensus()` for the whole flow of
* taking a snapshot, and its sub-actions.
* taking a snapshot, and its sub-actions. Tests the formatNumber and
* formatPercent methods.
*/
let utils = require("devtools/client/memory/utils");
@ -48,4 +49,28 @@ add_task(function *() {
ok(utils.breakdownEquals(utils.getCustomBreakdowns()["My Breakdown"], custom),
"utils.getCustomBreakdowns() returns custom breakdowns");
ok(true, "test formatNumber util functions");
equal(utils.formatNumber(12), "12", "formatNumber returns 12 for 12");
equal(utils.formatNumber(0), "0", "formatNumber returns 0 for 0");
equal(utils.formatNumber(-0), "0", "formatNumber returns 0 for -0");
equal(utils.formatNumber(+0), "0", "formatNumber returns 0 for +0");
equal(utils.formatNumber(1234567), "1 234 567",
"formatNumber adds a space every 3rd digit");
equal(utils.formatNumber(12345678), "12 345 678",
"formatNumber adds a space every 3rd digit");
equal(utils.formatNumber(123456789), "123 456 789",
"formatNumber adds a space every 3rd digit");
equal(utils.formatNumber(12, true), "+12",
"formatNumber can display number sign");
equal(utils.formatNumber(-12, true), "-12",
"formatNumber can display number sign (negative)");
ok(true, "test formatPercent util functions");
equal(utils.formatPercent(12), "12%", "formatPercent returns 12% for 12");
equal(utils.formatPercent(12345), "12 345%",
"formatPercent returns 12 345% for 12345");
});

View File

@ -517,6 +517,42 @@ exports.openFilePicker = function({ title, filters, defaultName, mode }) {
});
};
/**
* Format the provided number with a space every 3 digits, and optionally
* prefixed by its sign.
*
* @param {Number} number
* @param {Boolean} showSign (defaults to false)
*/
exports.formatNumber = function(number, showSign = false) {
const rounded = Math.round(number);
if (rounded === 0 || rounded === -0) {
return "0";
}
const abs = String(Math.abs(rounded));
// replace every digit followed by (sets of 3 digits) by (itself and a space)
const formatted = abs.replace(/(\d)(?=(\d{3})+$)/g, "$1 ");
if (showSign) {
const sign = rounded < 0 ? "-" : "+";
return sign + formatted;
}
return formatted;
};
/**
* Format the provided percentage following the same logic as formatNumber and
* an additional % suffix.
*
* @param {Number} percent
* @param {Boolean} showSign (defaults to false)
*/
exports.formatPercent = function(percent, showSign = false) {
return exports.L10N.getFormatStr("tree-item.percent",
exports.formatNumber(percent, showSign));
};
/**
* Creates a hash map mapping node IDs to its parent node.
*

View File

@ -2117,7 +2117,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
copyPostDataElement.hidden = !selectedItem || !selectedItem.attachment.requestPostData;
let copyAsCurlElement = $("#request-menu-context-copy-as-curl");
copyAsCurlElement.hidden = !selectedItem || !selectedItem.attachment.responseContent;
copyAsCurlElement.hidden = !selectedItem || !selectedItem.attachment;
let copyRequestHeadersElement = $("#request-menu-context-copy-request-headers");
copyRequestHeadersElement.hidden = !selectedItem || !selectedItem.attachment.requestHeaders;

View File

@ -127,6 +127,7 @@ skip-if = os == 'linux' || debug # bug 1186322 for Linux, bug 1203895 for leaks
[browser_perf-theme-toggle-01.js]
[browser_perf-telemetry.js]
[browser_profiler_tree-abstract-01.js]
skip-if = e10s && os == 'linux' # bug 1186322
[browser_profiler_tree-abstract-02.js]
[browser_profiler_tree-abstract-03.js]
[browser_profiler_tree-abstract-04.js]

View File

@ -1,9 +1,17 @@
/* 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";
var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const loaders = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
const { devtools, DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const { joinURI } = devtools.require("devtools/shared/path");
Cu.import("resource://gre/modules/AppConstants.jsm");
const BROWSER_BASED_DIRS = [
"resource://devtools/client/jsonview",
"resource://devtools/client/shared/vendor",
"resource://devtools/client/shared/components",
"resource://devtools/client/shared/redux"
@ -36,12 +44,18 @@ const BROWSER_BASED_DIRS = [
*/
function BrowserLoader(baseURI, window) {
const loaderOptions = devtools.require("@loader/options");
const dynamicPaths = {};
if(AppConstants.DEBUG || AppConstants.DEBUG_JS_MODULES) {
dynamicPaths["devtools/client/shared/vendor/react"] =
"resource://devtools/client/shared/vendor/react-dev";
};
const opts = {
id: "browser-loader",
sharedGlobal: true,
sandboxPrototype: window,
paths: Object.assign({}, loaderOptions.paths),
paths: Object.assign({}, dynamicPaths, loaderOptions.paths),
invisibleToDebugger: loaderOptions.invisibleToDebugger,
require: (id, require) => {
const uri = require.resolve(id);
@ -54,6 +68,13 @@ function BrowserLoader(baseURI, window) {
}
return require(uri);
},
globals: {
// Make sure 'define' function exists. This allows reusing AMD modules.
define: function(callback) {
callback(this.require, this.exports, this.module);
return this.exports;
}
}
};

View File

@ -1,24 +1,23 @@
React has a dev and prod version. The dev version includes additional
sanity checks and better errors, but at a slight perf cost. The prod
version available on the web is by default minified, but we don't want
a minified version, so we need to build it ourselves.
We have a version of React that has a few minor patches on top of it:
https://github.com/mozilla/react. These instructions are how to
upgrade to the latest version of React.
In bug 1217979, we are only using react in development environment for now until
we can think of a way to conditionally build these different versions, so
the `react.js` here is the dev environment one and generated with the following steps:
First, rebase our fork on top of the latest version and publish it to
our repo.
Next, build React. You should already have our fork locally; make sure
you are building our fork: https://github.com/mozilla/react
* git clone https://github.com/facebook/react.git && cd react
* npm install
* grunt build
* cp build/react-with-addons.js <gecko-dev>/devtools/client/shared/vendor/react.js
* cp build/react-with-addons.js <gecko-dev>/devtools/client/shared/vendor/react-dev.js
Note that the last command above adds a `react-dev.js` file. You also
need to generated a production version of React:
For production, which we do not currently have:
* NODE_ENV=production grunt build
* cp build/react-with-addons.js <gecko-dev>/devtools/client/shared/vendor/react.js
The second build produces a non-minified React file but with all the
sanity checks that incur a perf hit removed.
You also need to copy the ReactDOM package. It requires React, so
right now we are just manually changing the path from `react` to
`devtools/client/shared/vendor/react`.

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