mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
0219a9d227
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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", "");
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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 -->
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"/>
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"/>
|
||||
|
@ -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 -->
|
||||
|
@ -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"/>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
});
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
@ -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);
|
||||
});
|
228
browser/base/content/test/general/get_user_media_helpers.js
Normal file
228
browser/base/content/test/general/get_user_media_helpers.js
Normal 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);
|
||||
}
|
@ -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>");
|
@ -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
|
||||
|
@ -329,6 +329,7 @@ const PanelUI = {
|
||||
evt.initCustomEvent("ViewShowing", true, true, viewNode);
|
||||
viewNode.dispatchEvent(evt);
|
||||
if (evt.defaultPrevented) {
|
||||
aAnchor.open = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -10,6 +10,9 @@
|
||||
"XPCOMUtils": true,
|
||||
"Task": true,
|
||||
|
||||
// Browser window globals.
|
||||
"PanelUI": false,
|
||||
|
||||
// Test harness globals
|
||||
"ExtensionTestUtils": false,
|
||||
|
||||
|
@ -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]);
|
||||
});
|
||||
},
|
||||
];
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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");
|
||||
|
@ -63,6 +63,7 @@ add_task(function* () {
|
||||
|
||||
clickBrowserAction(extension);
|
||||
yield extension.awaitMessage("popup-finished");
|
||||
yield closeBrowserAction(extension);
|
||||
|
||||
yield extension.unload();
|
||||
});
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
|
@ -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 ],
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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">
|
||||
|
@ -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">
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -116,7 +116,6 @@ button:disabled {
|
||||
}
|
||||
|
||||
#certificateErrorReporting,
|
||||
#reportCertificateError,
|
||||
#reportSentMessage {
|
||||
display: none;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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",
|
||||
|
@ -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"));
|
||||
|
@ -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;
|
||||
|
@ -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"));
|
||||
|
@ -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"));
|
||||
|
@ -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"));
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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"));
|
||||
|
||||
|
@ -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"));
|
||||
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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"));
|
||||
|
@ -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.
|
||||
|
@ -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");
|
||||
|
@ -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"));
|
||||
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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");
|
||||
|
@ -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"));
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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"));
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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" },
|
||||
|
@ -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.
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
25
devtools/client/shared/vendor/REACT_UPGRADING
vendored
25
devtools/client/shared/vendor/REACT_UPGRADING
vendored
@ -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
Loading…
Reference in New Issue
Block a user