68.14.8 - toolkit

This commit is contained in:
Fedor 2025-04-19 19:15:41 +03:00
parent 0841e6d37b
commit 047e832886
906 changed files with 18400 additions and 34034 deletions

View File

@ -8,9 +8,6 @@ var EXPORTED_SYMBOLS = ["PictureInPictureChild", "PictureInPictureToggleChild"];
const { ActorChild } = ChromeUtils.import( const { ActorChild } = ChromeUtils.import(
"resource://gre/modules/ActorChild.jsm" "resource://gre/modules/ActorChild.jsm"
); );
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
ChromeUtils.defineModuleGetter( ChromeUtils.defineModuleGetter(
this, this,
@ -23,8 +20,6 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/Services.jsm" "resource://gre/modules/Services.jsm"
); );
XPCOMUtils.defineLazyGlobalGetters(this, ["InspectorUtils"]);
const TOGGLE_ENABLED_PREF = const TOGGLE_ENABLED_PREF =
"media.videocontrols.picture-in-picture.video-toggle.enabled"; "media.videocontrols.picture-in-picture.video-toggle.enabled";
const TOGGLE_TESTING_PREF = const TOGGLE_TESTING_PREF =
@ -149,6 +144,10 @@ class PictureInPictureToggleChild extends ActorChild {
} }
break; break;
} }
case "mouseout": {
this.onMouseOut(event);
break;
}
case "mousedown": case "mousedown":
case "pointerup": case "pointerup":
case "mouseup": case "mouseup":
@ -280,6 +279,9 @@ class PictureInPictureToggleChild extends ActorChild {
capture: true, capture: true,
}); });
this.content.windowRoot.addEventListener("click", this, { capture: true }); this.content.windowRoot.addEventListener("click", this, { capture: true });
this.content.windowRoot.addEventListener("mouseout", this, {
capture: true,
});
} }
removeMouseButtonListeners() { removeMouseButtonListeners() {
@ -298,6 +300,9 @@ class PictureInPictureToggleChild extends ActorChild {
this.content.windowRoot.removeEventListener("click", this, { this.content.windowRoot.removeEventListener("click", this, {
capture: true, capture: true,
}); });
this.content.windowRoot.removeEventListener("mouseout", this, {
capture: true,
});
} }
/** /**
@ -455,6 +460,28 @@ class PictureInPictureToggleChild extends ActorChild {
} }
} }
/**
* Called on mouseout events to determine whether or not the mouse has
* exited the window.
*
* @param {Event} event The mouseout event.
*/
onMouseOut(event) {
if (!event.relatedTarget) {
// For mouseout events, if there's no relatedTarget (which normally
// maps to the element that the mouse entered into) then this means that
// we left the window.
let state = this.docState;
let video = state.weakOverVideo && state.weakOverVideo.get();
if (!video) {
return;
}
this.onMouseLeaveVideo(video);
}
}
/** /**
* Called for each mousemove event when we're tracking those events to * Called for each mousemove event when we're tracking those events to
* determine if the cursor is hovering over a <video>. * determine if the cursor is hovering over a <video>.
@ -570,7 +597,7 @@ class PictureInPictureToggleChild extends ActorChild {
} }
state.weakOverVideo = Cu.getWeakReference(video); state.weakOverVideo = Cu.getWeakReference(video);
InspectorUtils.addPseudoClassLock(controlsOverlay, ":hover"); controlsOverlay.classList.add("hovering");
// Now that we're hovering the video, we'll check to see if we're // Now that we're hovering the video, we'll check to see if we're
// hovering the toggle too. // hovering the toggle too.
@ -579,18 +606,14 @@ class PictureInPictureToggleChild extends ActorChild {
/** /**
* Checks if a mouse event is happening over a toggle element. If it is, * Checks if a mouse event is happening over a toggle element. If it is,
* sets the :hover pseudoclass on it. Otherwise, it clears the :hover * sets the hovering class on it. Otherwise, it clears the hovering
* pseudoclass. * class.
* *
* @param {Element} toggle The Picture-in-Picture toggle to check. * @param {Element} toggle The Picture-in-Picture toggle to check.
* @param {MouseEvent} event A MouseEvent to test. * @param {MouseEvent} event A MouseEvent to test.
*/ */
checkHoverToggle(toggle, event) { checkHoverToggle(toggle, event) {
if (this.isMouseOverToggle(toggle, event)) { toggle.classList.toggle("hovering", this.isMouseOverToggle(toggle, event));
InspectorUtils.addPseudoClassLock(toggle, ":hover");
} else {
InspectorUtils.removePseudoClassLock(toggle, ":hover");
}
} }
/** /**
@ -606,14 +629,15 @@ class PictureInPictureToggleChild extends ActorChild {
if (shadowRoot) { if (shadowRoot) {
let controlsOverlay = shadowRoot.querySelector(".controlsOverlay"); let controlsOverlay = shadowRoot.querySelector(".controlsOverlay");
let toggle = shadowRoot.getElementById("pictureInPictureToggleButton"); let toggle = shadowRoot.getElementById("pictureInPictureToggleButton");
InspectorUtils.removePseudoClassLock(controlsOverlay, ":hover"); controlsOverlay.classList.remove("hovering");
InspectorUtils.removePseudoClassLock(toggle, ":hover"); toggle.classList.remove("hovering");
} }
state.weakOverVideo = null; state.weakOverVideo = null;
if (!this.toggleTesting) { if (!this.toggleTesting) {
state.hideToggleDeferredTask.disarm(); state.hideToggleDeferredTask.disarm();
state.mousemoveDeferredTask.disarm();
} }
state.hideToggleDeferredTask = null; state.hideToggleDeferredTask = null;

View File

@ -6,7 +6,7 @@
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta http-equiv="Content-Security-Policy" content="default-src chrome:" /> <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" />
<meta name="viewport" content="width=device-width"/> <meta name="viewport" content="width=device-width"/>
<title>Checkerboard Analyzer</title> <title>Checkerboard Analyzer</title>
<link rel="stylesheet" href="chrome://global/content/aboutCheckerboard.css" type="text/css"/> <link rel="stylesheet" href="chrome://global/content/aboutCheckerboard.css" type="text/css"/>

View File

@ -21,6 +21,11 @@ body {
margin: auto; margin: auto;
} }
/* The comment at the top of aboutMemory.xhtml explains this font choice. */
pre {
font-family: DejaVu Sans Mono, Liberation Mono, Fira Mono, monospace;
}
div.ancillary { div.ancillary {
margin: 0.5em 0; margin: 0.5em 0;
-moz-user-select: none; -moz-user-select: none;

View File

@ -4,9 +4,19 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this - 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/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html xmlns="http://www.w3.org/1999/xhtml"> <!-- We explicitly set the language to English, which more-or-less guarantees
that the box-drawing characters used are the correct width. Without that,
in a Japanese or Chinese locale we might end up with box-drawing
characters that are twice the width of English characters, which messes up
the tree layout. See bug 1561153 for details. Note that about:memory is
not localized, so setting it explicitly to English should be fine.
We also set the fonts in aboutMemory.css in such a way that maximizes the
chances that the font chosen supports the box-drawing chars.
-->
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head> <head>
<meta http-equiv="Content-Security-Policy" content="default-src chrome:" /> <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" />
<meta name="viewport" content="width=device-width"/> <meta name="viewport" content="width=device-width"/>
<title>Memory Analyzer</title> <title>Memory Analyzer</title>
<link rel="stylesheet" href="chrome://global/skin/aboutMemory.css" type="text/css"/> <link rel="stylesheet" href="chrome://global/skin/aboutMemory.css" type="text/css"/>

View File

@ -8,24 +8,24 @@ support-files =
memory-reports-diff1.json memory-reports-diff1.json
memory-reports-diff2.json memory-reports-diff2.json
memory-reports-good.json memory-reports-good.json
remote.xul remote.xhtml
[test_aboutmemory.xul] [test_aboutmemory.xhtml]
tags = clipboard tags = clipboard
[test_aboutmemory2.xul] [test_aboutmemory2.xhtml]
tags = clipboard tags = clipboard
[test_aboutmemory3.xul] [test_aboutmemory3.xhtml]
tags = clipboard tags = clipboard
[test_aboutmemory4.xul] [test_aboutmemory4.xhtml]
tags = clipboard tags = clipboard
[test_aboutmemory5.xul] [test_aboutmemory5.xhtml]
tags = clipboard tags = clipboard
skip-if = asan # Bug 1116230 skip-if = asan # Bug 1116230
[test_aboutmemory6.xul] [test_aboutmemory6.xhtml]
[test_aboutmemory7.xul] [test_aboutmemory7.xhtml]
tags = clipboard tags = clipboard
[test_memoryReporters.xul] [test_memoryReporters.xhtml]
[test_memoryReporters2.xul] [test_memoryReporters2.xhtml]
[test_sqliteMultiReporter.xul] [test_sqliteMultiReporter.xhtml]
[test_dumpGCAndCCLogsToFile.xul] [test_dumpGCAndCCLogsToFile.xhtml]
skip-if = (verify && debug && (os == 'mac')) skip-if = (verify && debug && (os == 'mac'))

View File

@ -7,7 +7,7 @@
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script> <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<!-- This file uses fake memory reporters to test the presentation of memory <!-- This file uses fake memory reporters to test the presentation of memory
reports in about:memory. test_memoryReporters.xul uses the real reports in about:memory. test_memoryReporters.xhtml uses the real
memory reporters to test whether the memory reporters are producing memory reporters to test whether the memory reporters are producing
sensible results. --> sensible results. -->

View File

@ -42,7 +42,7 @@
SpecialPowers.pushPrefEnv({"set": prefs}, function() { SpecialPowers.pushPrefEnv({"set": prefs}, function() {
for (let i = 0; i < numRemotes; i++) { for (let i = 0; i < numRemotes; i++) {
let w = remotes[i] = window.open("remote.xul", "", "chrome"); let w = remotes[i] = window.open("remote.xhtml", "", "chrome");
w.addEventListener("load", function loadHandler() { w.addEventListener("load", function loadHandler() {
w.removeEventListener("load", loadHandler); w.removeEventListener("load", loadHandler);

View File

@ -24,7 +24,7 @@
SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", numRemotes]]}, SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", numRemotes]]},
function() { function() {
for (let i = 0; i < numRemotes; i++) { for (let i = 0; i < numRemotes; i++) {
let w = remotes[i] = window.open("remote.xul", "", "chrome"); let w = remotes[i] = window.open("remote.xhtml", "", "chrome");
w.addEventListener("load", function loadHandler() { w.addEventListener("load", function loadHandler() {
w.removeEventListener("load", loadHandler); w.removeEventListener("load", loadHandler);

View File

@ -6,7 +6,7 @@
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<!-- This file tests (in a rough fashion) whether the memory reporters are <!-- This file tests (in a rough fashion) whether the memory reporters are
producing sensible results. test_aboutmemory.xul tests the producing sensible results. test_aboutmemory.xhtml tests the
presentation of memory reports in about:memory. --> presentation of memory reports in about:memory. -->
<!-- test results are displayed in the html:body --> <!-- test results are displayed in the html:body -->

View File

@ -31,7 +31,7 @@
let remotes = []; let remotes = [];
SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 3]]}, function() { SpecialPowers.pushPrefEnv({"set": [["dom.ipc.processCount", 3]]}, function() {
for (let i = 0; i < numToOpen; i++) { for (let i = 0; i < numToOpen; i++) {
let w = remotes[i] = window.open("remote.xul", "", "chrome"); let w = remotes[i] = window.open("remote.xhtml", "", "chrome");
w.addEventListener("load", function loadHandler() { w.addEventListener("load", function loadHandler() {
w.removeEventListener("load", loadHandler); w.removeEventListener("load", loadHandler);

View File

@ -6,7 +6,7 @@
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<meta http-equiv="Content-Security-Policy" content="default-src chrome:;img-src data:" /> <meta http-equiv="Content-Security-Policy" content="default-src chrome:;img-src data:; object-src 'none'" />
<title data-l10n-id="about-performance-title"/> <title data-l10n-id="about-performance-title"/>
<link rel="icon" type="image/svg+xml" id="favicon" <link rel="icon" type="image/svg+xml" id="favicon"
href="chrome://global/skin/icons/performance.svg"/> href="chrome://global/skin/icons/performance.svg"/>

View File

@ -4,5 +4,5 @@
toolkit.jar: toolkit.jar:
content/global/alerts/alert.css (resources/content/alert.css) content/global/alerts/alert.css (resources/content/alert.css)
content/global/alerts/alert.xul (resources/content/alert.xul) content/global/alerts/alert.xhtml (resources/content/alert.xhtml)
content/global/alerts/alert.js (resources/content/alert.js) content/global/alerts/alert.js (resources/content/alert.js)

View File

@ -5,7 +5,6 @@
#include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/Preferences.h" #include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
#include "nsXULAppAPI.h" #include "nsXULAppAPI.h"
#include "nsAlertsService.h" #include "nsAlertsService.h"
@ -290,9 +289,6 @@ NS_IMETHODIMP nsAlertsService::SetManualDoNotDisturb(bool aDoNotDisturb) {
NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED); NS_ENSURE_TRUE(alertsDND, NS_ERROR_NOT_IMPLEMENTED);
nsresult rv = alertsDND->SetManualDoNotDisturb(aDoNotDisturb); nsresult rv = alertsDND->SetManualDoNotDisturb(aDoNotDisturb);
if (NS_SUCCEEDED(rv)) {
Telemetry::Accumulate(Telemetry::ALERTS_SERVICE_DND_ENABLED, 1);
}
return rv; return rv;
#endif #endif
} }

View File

@ -17,7 +17,7 @@
using namespace mozilla; using namespace mozilla;
#define ALERT_CHROME_URL "chrome://global/content/alerts/alert.xul" #define ALERT_CHROME_URL "chrome://global/content/alerts/alert.xhtml"
namespace { namespace {
StaticRefPtr<nsXULAlerts> gXULAlerts; StaticRefPtr<nsXULAlerts> gXULAlerts;

View File

@ -8,7 +8,7 @@
animation-name: alert-animation; animation-name: alert-animation;
} }
#alertBox[animate]:not([clicked]):not([closing]):hover { #alertBox[animate]:not([clicked], [closing]):hover {
animation-play-state: paused; animation-play-state: paused;
} }

View File

@ -62,6 +62,9 @@
<!-- This method is called inline because we want to make sure we establish the width <!-- This method is called inline because we want to make sure we establish the width
and height of the alert before we fire the onload handler. --> and height of the alert before we fire the onload handler. -->
<script>prefillAlertInfo();</script> <script>
/* eslint-disable no-undef */
prefillAlertInfo();
</script>
</window> </window>

View File

@ -23,12 +23,11 @@ const chromeScript = SpecialPowers.loadChromeScript(_ => {
sendAsyncMessage("waitForXULAlert", false); sendAsyncMessage("waitForXULAlert", false);
}, 2000); }, 2000);
var windowObserver = function(aSubject, aTopic, aData) { var windowObserver = function(win, aTopic, aData) {
if (aTopic != "domwindowopened") { if (aTopic != "domwindowopened") {
return; return;
} }
var win = aSubject.QueryInterface(Ci.nsIDOMWindow);
win.addEventListener("load", function() { win.addEventListener("load", function() {
let windowType = win.document.documentElement.getAttribute("windowtype"); let windowType = win.document.documentElement.getAttribute("windowtype");
if (windowType == "alert:alert") { if (windowType == "alert:alert") {

View File

@ -26,7 +26,7 @@ const chromeScript = SpecialPowers.loadChromeScript(_ => {
sendAsyncMessage("waitedForPosition", null); sendAsyncMessage("waitedForPosition", null);
}, 2000); }, 2000);
var windowObserver = function(aSubject, aTopic, aData) { var windowObserver = function(win, aTopic, aData) {
if (aTopic != "domwindowopened") { if (aTopic != "domwindowopened") {
return; return;
} }
@ -36,7 +36,6 @@ const chromeScript = SpecialPowers.loadChromeScript(_ => {
Services.ww.unregisterNotification(windowObserver); Services.ww.unregisterNotification(windowObserver);
var win = aSubject.QueryInterface(Ci.nsIDOMWindow);
win.addEventListener("pageshow", function() { win.addEventListener("pageshow", function() {
var x = win.screenX; var x = win.screenX;
var y = win.screenY; var y = win.screenY;

View File

@ -63,7 +63,7 @@ AppPicker.prototype = {
.setAttribute("value", description); .setAttribute("value", description);
document document
.getElementById("suggested-filename") .getElementById("suggested-filename")
.setAttribute("value", filename); .setAttribute("value", filename || "");
document document
.getElementById("content-icon") .getElementById("content-icon")
.setAttribute( .setAttribute(

View File

@ -7,16 +7,17 @@
<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
<?xml-stylesheet href="chrome://global/skin/appPicker.css" type="text/css"?> <?xml-stylesheet href="chrome://global/skin/appPicker.css" type="text/css"?>
<!DOCTYPE dialog SYSTEM "chrome://global/locale/appPicker.dtd" > <!DOCTYPE window SYSTEM "chrome://global/locale/appPicker.dtd" >
<dialog id="app-picker" <window
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="g_dialog.appPickerLoad();" onload="g_dialog.appPickerLoad();"
buttons="accept,cancel,extra2"
buttonlabelextra2="&BrowseButton.label;"
defaultButton="cancel"
aria-describedby="content-description suggested-filename" aria-describedby="content-description suggested-filename"
persist="screenX screenY"> persist="screenX screenY">
<dialog id="app-picker"
buttons="accept,cancel,extra2"
buttonlabelextra2="&BrowseButton.label;"
defaultButton="cancel">
<script src="chrome://global/content/appPicker.js"/> <script src="chrome://global/content/appPicker.js"/>
@ -35,3 +36,4 @@
<label id="app-picker-notfound" value="&NoAppFound.label;" hidden="true"/> <label id="app-picker-notfound" value="&NoAppFound.label;" hidden="true"/>
</dialog> </dialog>
</window>

View File

@ -3,6 +3,6 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
toolkit.jar: toolkit.jar:
content/global/appPicker.xul (content/appPicker.xul) content/global/appPicker.xhtml (content/appPicker.xhtml)
content/global/appPicker.js (content/appPicker.js) content/global/appPicker.js (content/appPicker.js)

View File

@ -1283,12 +1283,13 @@ nsresult nsAutoCompleteController::EnterMatch(bool aIsPopupSelection,
} }
obsSvc->NotifyObservers(input, "autocomplete-did-enter-text", nullptr); obsSvc->NotifyObservers(input, "autocomplete-did-enter-text", nullptr);
ClosePopup();
bool cancel; bool cancel;
bool itemWasSelected = selectedIndex >= 0 && !value.IsEmpty(); bool itemWasSelected = selectedIndex >= 0 && !value.IsEmpty();
input->OnTextEntered(aEvent, itemWasSelected, &cancel); input->OnTextEntered(aEvent, itemWasSelected, &cancel);
ClosePopup();
return NS_OK; return NS_OK;
} }

View File

@ -1068,7 +1068,7 @@ nsWebBrowser::SetParentNativeWindow(nativeWindow aParentNativeWindow) {
NS_IMETHODIMP NS_IMETHODIMP
nsWebBrowser::GetNativeHandle(nsAString& aNativeHandle) { nsWebBrowser::GetNativeHandle(nsAString& aNativeHandle) {
// the nativeHandle should be accessed from nsIXULWindow // the nativeHandle should be accessed from nsIAppWindow
return NS_ERROR_NOT_IMPLEMENTED; return NS_ERROR_NOT_IMPLEMENTED;
} }

View File

@ -7,7 +7,7 @@
<html> <html>
<head> <head>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Security-Policy" content="default-src chrome:" /> <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" />
<title>about:certificate</title> <title>about:certificate</title>
</head> </head>
<body> <body>

View File

@ -555,8 +555,8 @@ _ContextualIdentityService.prototype = {
} }
let tabbrowser = win.gBrowser; let tabbrowser = win.gBrowser;
for (let i = tabbrowser.tabContainer.children.length - 1; i >= 0; --i) { for (let i = tabbrowser.tabs.length - 1; i >= 0; --i) {
let tab = tabbrowser.tabContainer.children[i]; let tab = tabbrowser.tabs[i];
if ( if (
tab.hasAttribute("usercontextid") && tab.hasAttribute("usercontextid") &&
(!userContextId || (!userContextId ||

View File

@ -1,90 +0,0 @@
/* 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 { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
const { FileUtils } = ChromeUtils.import(
"resource://gre/modules/FileUtils.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyServiceGetters(this, {
gCertDB: ["@mozilla.org/security/x509certdb;1", "nsIX509CertDB"],
});
var EXPORTED_SYMBOLS = ["Corroborate"];
/**
* Tools for verifying internal files in Mozilla products.
*/
this.Corroborate = {
async init() {
const appOmniJar = FileUtils.getFile("XCurProcD", [
AppConstants.OMNIJAR_NAME,
]);
const greOmniJar = FileUtils.getFile("GreD", [AppConstants.OMNIJAR_NAME]);
const systemAddons = FileUtils.getFile("XCurProcD", ["features"]);
let corruptOmnijar = true;
// If an omni jar is missing, we consider that corrupt. Firefox could be running with
// an omni jar unpacked, but it would never be signed correctly in that case so there
// isn't a point checking further.
if (appOmniJar.exists() && greOmniJar.exists()) {
corruptOmnijar = !(
(await this.verifyJar(appOmniJar)) && (await this.verifyJar(greOmniJar))
);
}
// It's not necessarily a problem if all built-in system add-ons have been removed,
// more that we want to know if any unsigned add-ons are present. The Telemetry Environment
// shows which system add-ons are present, anyway.
let corruptSystemAddons = false;
for (let file of systemAddons.directoryEntries) {
if (!(await this.verifyJar(file))) {
corruptSystemAddons = true;
break;
}
}
this.reportTelemetry(corruptOmnijar, corruptSystemAddons);
},
/**
* Verify signed state of arbitrary JAR file. Currently only JAR files signed
* with Mozilla-internal keys are supported.
*
* @argument file - an nsIFile pointing to the JAR to verify.
*
* @returns {Promise} - resolves true if file exists and is valid, false otherwise.
* Never rejects.
*/
verifyJar(file) {
let root = Ci.nsIX509CertDB.AddonsPublicRoot;
let expectedOrganizationalUnit = "Mozilla Components";
return new Promise(resolve => {
gCertDB.openSignedAppFileAsync(root, file, (rv, _zipReader, cert) => {
resolve(
Components.isSuccessCode(rv) &&
cert.organizationalUnit === expectedOrganizationalUnit
);
});
});
},
reportTelemetry(corruptOmnijar, corruptSystemAddons) {
Services.telemetry.scalarSet(
"corroborate.omnijar_corrupted",
corruptOmnijar
);
Services.telemetry.scalarSet(
"corroborate.system_addons_corrupted",
corruptSystemAddons
);
},
};

View File

@ -1,15 +0,0 @@
# 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/.
with Files('**'):
BUG_COMPONENT = ('Toolkit', 'General')
EXTRA_JS_MODULES += [
'Corroborate.jsm',
]
XPCSHELL_TESTS_MANIFESTS += [
'test/xpcshell/xpcshell.ini',
]

View File

@ -1,49 +0,0 @@
"use strict";
const { Corroborate } = ChromeUtils.import(
"resource://gre/modules/Corroborate.jsm"
);
const { FileUtils } = ChromeUtils.import(
"resource://gre/modules/FileUtils.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
add_task(async function test_various_jars() {
let result = await Corroborate.verifyJar(do_get_file("data/unsigned.xpi"));
equal(result, false, "unsigned files do not verify");
result = await Corroborate.verifyJar(do_get_file("data/signed-amo.xpi"));
equal(result, false, "AMO signed files do not verify");
result = await Corroborate.verifyJar(
do_get_file("data/signed-privileged.xpi")
);
equal(result, false, "Privileged signed files do not verify");
let missingFile = do_get_file("data");
missingFile.append("missing.xpi");
result = await Corroborate.verifyJar(missingFile);
equal(result, false, "Missing (but expected) files do not verify");
result = await Corroborate.verifyJar(
do_get_file("data/signed-components.xpi")
);
equal(result, true, "Components signed files do verify");
});
add_task(async function test_telemetry() {
Corroborate.reportTelemetry(true, false);
const scalars = Services.telemetry.getSnapshotForScalars("main", false)
.parent;
ok(
"corroborate.omnijar_corrupted" in scalars,
"omni jar should be corrupted"
);
ok(
"corroborate.system_addons_corrupted" in scalars,
"system addons should not be corrupted"
);
});

View File

@ -1,7 +0,0 @@
[DEFAULT]
tags = corroborator
support-files =
data/**
[test_verify_jar.js]
skip-if = true # Bug 1549147 - Disable temporily until non-expired cert is available to sign test XPI.

View File

@ -5,5 +5,5 @@ support-files =
ctypes_worker.js ctypes_worker.js
../unit/test_jsctypes.js ../unit/test_jsctypes.js
[test_ctypes.xul] [test_ctypes.xhtml]
skip-if = verify skip-if = verify

View File

@ -5,7 +5,8 @@
<window title="DOM Worker Threads Test" <window title="DOM Worker Threads Test"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="test();"> onload="test();"
onunload="onunload();">
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
<script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/> <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
@ -39,7 +40,7 @@
.get("CurWorkD", Ci.nsIFile); .get("CurWorkD", Ci.nsIFile);
path = location.pathname; path = location.pathname;
path = path.slice("content/".length, path = path.slice("content/".length,
-1 * "/test_ctypes.xul".length); -1 * "/test_ctypes.xhtml".length);
let components = path.split("/"); let components = path.split("/");
for (let part in components) { for (let part in components) {
dir.append(components[part]); dir.append(components[part]);
@ -71,6 +72,8 @@
{ {
SimpleTest.waitForExplicitFinish(); SimpleTest.waitForExplicitFinish();
Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
var dir = getCurrentDir(location.path); var dir = getCurrentDir(location.path);
ok(dir.exists() && dir.isDirectory(), "Chrome test dir doesn't exist?!"); ok(dir.exists() && dir.isDirectory(), "Chrome test dir doesn't exist?!");
setupLibs(dir); setupLibs(dir);
@ -97,6 +100,11 @@
worker.postMessage({dir: dir.path, os: Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS}); worker.postMessage({dir: dir.path, os: Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS});
} }
function onunload()
{
Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
}
]]> ]]>
</script> </script>

View File

@ -2852,7 +2852,7 @@ function run_FunctionType_tests() {
Assert.equal(f_t.size, undefined); Assert.equal(f_t.size, undefined);
Assert.ok(f_t.abi === ctypes.default_abi); Assert.ok(f_t.abi === ctypes.default_abi);
Assert.ok(f_t.returnType === g_t); Assert.ok(f_t.returnType === g_t);
Assert.ok(f_t.argTypes.length == 0); Assert.ok(!f_t.argTypes.length);
Assert.equal(f_t.toString(), "type " + name); Assert.equal(f_t.toString(), "type " + name);
Assert.equal(f_t.toSource(), "ctypes.FunctionType(ctypes.default_abi, g_t)"); Assert.equal(f_t.toSource(), "ctypes.FunctionType(ctypes.default_abi, g_t)");

View File

@ -743,7 +743,6 @@ this.DownloadHistoryList.prototype = {
}, },
// nsINavHistoryResultObserver // nsINavHistoryResultObserver
nodeAnnotationChanged() {},
nodeIconChanged() {}, nodeIconChanged() {},
nodeTitleChanged() {}, nodeTitleChanged() {},
nodeKeywordChanged() {}, nodeKeywordChanged() {},

View File

@ -50,7 +50,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
ExtensionProcessScript: "resource://gre/modules/ExtensionProcessScript.jsm", ExtensionProcessScript: "resource://gre/modules/ExtensionProcessScript.jsm",
ExtensionStorage: "resource://gre/modules/ExtensionStorage.jsm", ExtensionStorage: "resource://gre/modules/ExtensionStorage.jsm",
ExtensionStorageIDB: "resource://gre/modules/ExtensionStorageIDB.jsm", ExtensionStorageIDB: "resource://gre/modules/ExtensionStorageIDB.jsm",
ExtensionTelemetry: "resource://gre/modules/ExtensionTelemetry.jsm",
FileSource: "resource://gre/modules/L10nRegistry.jsm", FileSource: "resource://gre/modules/L10nRegistry.jsm",
L10nRegistry: "resource://gre/modules/L10nRegistry.jsm", L10nRegistry: "resource://gre/modules/L10nRegistry.jsm",
LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm", LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
@ -147,6 +146,7 @@ const PRIVILEGED_PERMS = new Set([
"mozillaAddons", "mozillaAddons",
"geckoViewAddons", "geckoViewAddons",
"telemetry", "telemetry",
"urlbar",
]); ]);
/** /**
@ -937,7 +937,7 @@ class ExtensionData {
// Check if there's a root directory `/localization` in the langpack. // Check if there's a root directory `/localization` in the langpack.
// If there is one, add it with the name `toolkit` as a FileSource. // If there is one, add it with the name `toolkit` as a FileSource.
const entries = await this.readDirectory("localization"); const entries = await this.readDirectory("localization");
if (entries.length > 0) { if (entries.length) {
l10nRegistrySources.toolkit = ""; l10nRegistrySources.toolkit = "";
} }
@ -1271,8 +1271,6 @@ class ExtensionData {
* Origin permissions requested. * Origin permissions requested.
* @param {array<string>} info.permissions.permissions * @param {array<string>} info.permissions.permissions
* Regular (non-origin) permissions requested. * Regular (non-origin) permissions requested.
* @param {boolean} info.unsigned
* True if the prompt is for installing an unsigned addon.
* @param {string} info.type * @param {string} info.type
* The type of prompt being shown. May be one of "update", * The type of prompt being shown. May be one of "update",
* "sideload", "optional", or omitted for a regular * "sideload", "optional", or omitted for a regular
@ -1404,9 +1402,6 @@ class ExtensionData {
"webextPerms.header", "webextPerms.header",
["<>"] ["<>"]
); );
result.text = info.unsigned
? bundle.GetStringFromName("webextPerms.unsignedWarning")
: "";
result.listIntro = bundle.GetStringFromName("webextPerms.listIntro"); result.listIntro = bundle.GetStringFromName("webextPerms.listIntro");
result.acceptText = bundle.GetStringFromName("webextPerms.add.label"); result.acceptText = bundle.GetStringFromName("webextPerms.add.label");
@ -1423,10 +1418,9 @@ class ExtensionData {
"webextPerms.sideloadHeader", "webextPerms.sideloadHeader",
["<>"] ["<>"]
); );
let key = let key = !result.msgs.length
result.msgs.length == 0 ? "webextPerms.sideloadTextNoPerms"
? "webextPerms.sideloadTextNoPerms" : "webextPerms.sideloadText2";
: "webextPerms.sideloadText2";
result.text = bundle.GetStringFromName(key); result.text = bundle.GetStringFromName(key);
result.acceptText = bundle.GetStringFromName( result.acceptText = bundle.GetStringFromName(
"webextPerms.sideloadEnable.label" "webextPerms.sideloadEnable.label"
@ -1672,7 +1666,7 @@ class Extension extends ExtensionData {
this.permissions.add(perm); this.permissions.add(perm);
} }
if (permissions.origins.length > 0) { if (permissions.origins.length) {
let patterns = this.whiteListedHosts.patterns.map(host => host.pattern); let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
this.whiteListedHosts = new MatchPatternSet( this.whiteListedHosts = new MatchPatternSet(
@ -1883,8 +1877,25 @@ class Extension extends ExtensionData {
return manifest; return manifest;
} }
get contentSecurityPolicy() { get extensionPageCSP() {
return this.manifest.content_security_policy; const { content_security_policy } = this.manifest;
if (
content_security_policy &&
typeof content_security_policy === "object"
) {
return content_security_policy.extension_pages;
}
return content_security_policy;
}
get contentScriptCSP() {
let { content_security_policy } = this.manifest;
if (
content_security_policy &&
typeof content_security_policy === "object"
) {
return content_security_policy.content_scripts;
}
} }
get backgroundScripts() { get backgroundScripts() {
@ -1911,7 +1922,8 @@ class Extension extends ExtensionData {
id: this.id, id: this.id,
uuid: this.uuid, uuid: this.uuid,
name: this.name, name: this.name,
contentSecurityPolicy: this.contentSecurityPolicy, extensionPageCSP: this.extensionPageCSP,
contentScriptCSP: this.contentScriptCSP,
instanceId: this.instanceId, instanceId: this.instanceId,
resourceURL: this.resourceURL, resourceURL: this.resourceURL,
contentScripts: this.contentScripts, contentScripts: this.contentScripts,
@ -2174,7 +2186,6 @@ class Extension extends ExtensionData {
}); });
sharedData.set("extensions/pending", pendingExtensions); sharedData.set("extensions/pending", pendingExtensions);
ExtensionTelemetry.extensionStartup.stopwatchStart(this);
try { try {
this.state = "Startup: Loading manifest"; this.state = "Startup: Loading manifest";
await this.loadManifest(); await this.loadManifest();
@ -2279,7 +2290,6 @@ class Extension extends ExtensionData {
throw errors; throw errors;
} finally { } finally {
ExtensionTelemetry.extensionStartup.stopwatchFinish(this);
} }
} }
@ -2486,7 +2496,7 @@ class Langpack extends ExtensionData {
async startup(reason) { async startup(reason) {
this.chromeRegistryHandle = null; this.chromeRegistryHandle = null;
if (this.startupData.chromeEntries.length > 0) { if (this.startupData.chromeEntries.length) {
const manifestURI = Services.io.newURI( const manifestURI = Services.io.newURI(
"manifest.json", "manifest.json",
null, null,

View File

@ -786,7 +786,7 @@ class BrowserExtensionContent extends EventEmitter {
/* eslint-disable mozilla/balanced-listeners */ /* eslint-disable mozilla/balanced-listeners */
this.on("add-permissions", (ignoreEvent, permissions) => { this.on("add-permissions", (ignoreEvent, permissions) => {
if (permissions.permissions.length > 0) { if (permissions.permissions.length) {
let perms = new Set(this.policy.permissions); let perms = new Set(this.policy.permissions);
for (let perm of permissions.permissions) { for (let perm of permissions.permissions) {
perms.add(perm); perms.add(perm);
@ -794,7 +794,7 @@ class BrowserExtensionContent extends EventEmitter {
this.policy.permissions = perms; this.policy.permissions = perms;
} }
if (permissions.origins.length > 0) { if (permissions.origins.length) {
let patterns = this.whiteListedHosts.patterns.map(host => host.pattern); let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
this.policy.allowedOrigins = new MatchPatternSet( this.policy.allowedOrigins = new MatchPatternSet(
@ -805,7 +805,7 @@ class BrowserExtensionContent extends EventEmitter {
}); });
this.on("remove-permissions", (ignoreEvent, permissions) => { this.on("remove-permissions", (ignoreEvent, permissions) => {
if (permissions.permissions.length > 0) { if (permissions.permissions.length) {
let perms = new Set(this.policy.permissions); let perms = new Set(this.policy.permissions);
for (let perm of permissions.permissions) { for (let perm of permissions.permissions) {
perms.delete(perm); perms.delete(perm);
@ -813,7 +813,7 @@ class BrowserExtensionContent extends EventEmitter {
this.policy.permissions = perms; this.policy.permissions = perms;
} }
if (permissions.origins.length > 0) { if (permissions.origins.length) {
let origins = permissions.origins.map( let origins = permissions.origins.map(
origin => new MatchPattern(origin, { ignorePath: true }).pattern origin => new MatchPattern(origin, { ignorePath: true }).pattern
); );
@ -1135,7 +1135,7 @@ class ChildAPIManager {
this.permissionsChangedCallbacks = new Set(); this.permissionsChangedCallbacks = new Set();
this.updatePermissions = null; this.updatePermissions = null;
if (this.context.extension.optionalPermissions.length > 0) { if (this.context.extension.optionalPermissions.length) {
this.updatePermissions = () => { this.updatePermissions = () => {
for (let callback of this.permissionsChangedCallbacks) { for (let callback of this.permissionsChangedCallbacks) {
try { try {

View File

@ -1344,7 +1344,6 @@ class SchemaAPIManager extends EventEmitter {
* "addon" - An addon process. * "addon" - An addon process.
* "content" - A content process. * "content" - A content process.
* "devtools" - A devtools process. * "devtools" - A devtools process.
* "proxy" - A proxy script process.
* @param {SchemaRoot} schema * @param {SchemaRoot} schema
*/ */
constructor(processType, schema) { constructor(processType, schema) {
@ -2152,9 +2151,6 @@ class EventManager {
this.remove = new Map(); this.remove = new Map();
if (this.persistent) { if (this.persistent) {
if (this.context.viewType !== "background") {
this.persistent = null;
}
if (AppConstants.DEBUG) { if (AppConstants.DEBUG) {
if (this.context.envType !== "addon_parent") { if (this.context.envType !== "addon_parent") {
throw new Error( throw new Error(
@ -2167,6 +2163,9 @@ class EventManager {
); );
} }
} }
if (this.context.viewType !== "background") {
this.persistent = null;
}
} }
} }
@ -2188,10 +2187,13 @@ class EventManager {
* the object also has a `primed` property that holds the things needed * the object also has a `primed` property that holds the things needed
* to handle events during startup and eventually connect the listener * to handle events during startup and eventually connect the listener
* with a callback registered from the extension. * with a callback registered from the extension.
*
* @param {Extension} extension
* @returns {boolean} True if the extension had any persistent listeners.
*/ */
static _initPersistentListeners(extension) { static _initPersistentListeners(extension) {
if (extension.persistentListeners) { if (extension.persistentListeners) {
return; return false;
} }
let listeners = new DefaultMap(() => new DefaultMap(() => new Map())); let listeners = new DefaultMap(() => new DefaultMap(() => new Map()));
@ -2199,9 +2201,10 @@ class EventManager {
let { persistentListeners } = extension.startupData; let { persistentListeners } = extension.startupData;
if (!persistentListeners) { if (!persistentListeners) {
return; return false;
} }
let found = false;
for (let [module, entry] of Object.entries(persistentListeners)) { for (let [module, entry] of Object.entries(persistentListeners)) {
for (let [event, paramlists] of Object.entries(entry)) { for (let [event, paramlists] of Object.entries(entry)) {
for (let paramlist of paramlists) { for (let paramlist of paramlists) {
@ -2210,9 +2213,11 @@ class EventManager {
.get(module) .get(module)
.get(event) .get(event)
.set(key, { params: paramlist }); .set(key, { params: paramlist });
found = true;
} }
} }
} }
return found;
} }
// Extract just the information needed at startup for all persistent // Extract just the information needed at startup for all persistent
@ -2239,16 +2244,29 @@ class EventManager {
// This function is only called during browser startup, it stores details // This function is only called during browser startup, it stores details
// about all primed listeners in the extension's persistentListeners Map. // about all primed listeners in the extension's persistentListeners Map.
static primeListeners(extension) { static primeListeners(extension) {
EventManager._initPersistentListeners(extension); if (!EventManager._initPersistentListeners(extension)) {
return;
}
let bgStartupPromise = new Promise(resolve => {
function resolveBgPromise(type) {
extension.off("startup", resolveBgPromise);
extension.off("background-page-aborted", resolveBgPromise);
extension.off("shutdown", resolveBgPromise);
resolve();
}
extension.on("startup", resolveBgPromise);
extension.on("background-page-aborted", resolveBgPromise);
extension.on("shutdown", resolveBgPromise);
});
for (let [module, moduleEntry] of extension.persistentListeners) { for (let [module, moduleEntry] of extension.persistentListeners) {
let api = extension.apiManager.getAPI(module, extension, "addon_parent"); let api = extension.apiManager.getAPI(module, extension, "addon_parent");
for (let [event, eventEntry] of moduleEntry) { for (let [event, eventEntry] of moduleEntry) {
for (let listener of eventEntry.values()) { for (let listener of eventEntry.values()) {
let primed = { pendingEvents: [], cleared: false }; let primed = { pendingEvents: [] };
listener.primed = primed; listener.primed = primed;
let bgStartupPromise = new Promise(r => extension.once("startup", r));
let wakeup = () => { let wakeup = () => {
extension.emit("background-page-event"); extension.emit("background-page-event");
return bgStartupPromise; return bgStartupPromise;
@ -2256,8 +2274,8 @@ class EventManager {
let fireEvent = (...args) => let fireEvent = (...args) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
if (primed.cleared) { if (!listener.primed) {
reject(new Error("listener not re-registered")); reject(new Error("primed listener not re-registered"));
return; return;
} }
primed.pendingEvents.push({ args, resolve, reject }); primed.pendingEvents.push({ args, resolve, reject });
@ -2311,6 +2329,7 @@ class EventManager {
if (!primed) { if (!primed) {
continue; continue;
} }
listener.primed = null;
for (let evt of primed.pendingEvents) { for (let evt of primed.pendingEvents) {
evt.reject(new Error("listener not re-registered")); evt.reject(new Error("listener not re-registered"));
@ -2320,7 +2339,6 @@ class EventManager {
EventManager.clearPersistentListener(extension, module, event, key); EventManager.clearPersistentListener(extension, module, event, key);
} }
primed.unregister(); primed.unregister();
primed.cleared = true;
} }
} }
} }

View File

@ -12,7 +12,6 @@ const { XPCOMUtils } = ChromeUtils.import(
XPCOMUtils.defineLazyModuleGetters(this, { XPCOMUtils.defineLazyModuleGetters(this, {
ExtensionProcessScript: "resource://gre/modules/ExtensionProcessScript.jsm", ExtensionProcessScript: "resource://gre/modules/ExtensionProcessScript.jsm",
ExtensionTelemetry: "resource://gre/modules/ExtensionTelemetry.jsm",
LanguageDetector: "resource:///modules/translation/LanguageDetector.jsm", LanguageDetector: "resource:///modules/translation/LanguageDetector.jsm",
MessageChannel: "resource://gre/modules/MessageChannel.jsm", MessageChannel: "resource://gre/modules/MessageChannel.jsm",
Schemas: "resource://gre/modules/Schemas.jsm", Schemas: "resource://gre/modules/Schemas.jsm",
@ -352,7 +351,7 @@ class Script {
} }
get requiresCleanup() { get requiresCleanup() {
return !this.removeCSS && (this.css.length > 0 || this.cssCodeHash); return !this.removeCSS && (!!this.css.length || this.cssCodeHash);
} }
async addCSSCode(cssCode) { async addCSSCode(cssCode) {
@ -527,7 +526,7 @@ class Script {
// problem since there are no network loads involved, and since we cache // problem since there are no network loads involved, and since we cache
// the stylesheets on first load. We should fix this up if it does becomes // the stylesheets on first load. We should fix this up if it does becomes
// a problem. // a problem.
if (this.css.length > 0) { if (this.css.length) {
context.contentWindow.document.blockParsing(cssPromise, { context.contentWindow.document.blockParsing(cssPromise, {
blockScriptCreated: false, blockScriptCreated: false,
}); });
@ -546,10 +545,6 @@ class Script {
// The evaluations below may throw, in which case the promise will be // The evaluations below may throw, in which case the promise will be
// automatically rejected. // automatically rejected.
ExtensionTelemetry.contentScriptInjection.stopwatchStart(
extension,
context
);
try { try {
for (let script of scripts) { for (let script of scripts) {
result = script.executeInGlobal(context.cloneScope); result = script.executeInGlobal(context.cloneScope);
@ -559,14 +554,12 @@ class Script {
result = Cu.evalInSandbox( result = Cu.evalInSandbox(
this.matcher.jsCode, this.matcher.jsCode,
context.cloneScope, context.cloneScope,
"latest" "latest",
"sandbox eval code",
1
); );
} }
} finally { } finally {
ExtensionTelemetry.contentScriptInjection.stopwatchFinish(
extension,
context
);
} }
await cssPromise; await cssPromise;
@ -666,7 +659,6 @@ class UserScript extends Script {
// The evaluations below may throw, in which case the promise will be // The evaluations below may throw, in which case the promise will be
// automatically rejected. // automatically rejected.
ExtensionTelemetry.userScriptInjection.stopwatchStart(extension, context);
try { try {
let userScriptSandbox = this.sandboxes.get(context); let userScriptSandbox = this.sandboxes.get(context);
@ -693,10 +685,6 @@ class UserScript extends Script {
script.executeInGlobal(userScriptSandbox); script.executeInGlobal(userScriptSandbox);
} }
} finally { } finally {
ExtensionTelemetry.userScriptInjection.stopwatchFinish(
extension,
context
);
} }
} }

View File

@ -132,14 +132,14 @@ let apiManager = new (class extends SchemaAPIManager {
let disabledIds = AddonManager.getStartupChanges( let disabledIds = AddonManager.getStartupChanges(
AddonManager.STARTUP_CHANGE_DISABLED AddonManager.STARTUP_CHANGE_DISABLED
); );
if (disabledIds.length > 0) { if (disabledIds.length) {
this._callHandlers(disabledIds, "disable", "onDisable"); this._callHandlers(disabledIds, "disable", "onDisable");
} }
let uninstalledIds = AddonManager.getStartupChanges( let uninstalledIds = AddonManager.getStartupChanges(
AddonManager.STARTUP_CHANGE_UNINSTALLED AddonManager.STARTUP_CHANGE_UNINSTALLED
); );
if (uninstalledIds.length > 0) { if (uninstalledIds.length) {
this._callHandlers(uninstalledIds, "uninstall", "onUninstall"); this._callHandlers(uninstalledIds, "uninstall", "onUninstall");
} }
} }
@ -504,7 +504,7 @@ ProxyMessenger = {
apiManager.global.tabGetSender(extension, target, sender); apiManager.global.tabGetSender(extension, target, sender);
} }
let promise1 = MessageChannel.sendMessage(receiverMM, messageName, data, { let promise = MessageChannel.sendMessage(receiverMM, messageName, data, {
sender, sender,
recipient, recipient,
responseType, responseType,
@ -529,7 +529,7 @@ ProxyMessenger = {
receiverMM receiverMM
); );
port.register(); port.register();
promise1.catch(() => { promise.catch(() => {
port.unregister(); port.unregister();
}); });
} }
@ -540,49 +540,7 @@ ProxyMessenger = {
} }
} }
if (!(recipient.toProxyScript && extension.remote)) { return promise;
return promise1;
}
// Proxy scripts run in the parent process so we need to dispatch
// the message to both the parent and extension process and merge
// the results.
// Once proxy scripts are gone (bug 1443259) we can remove this
let promise2 = MessageChannel.sendMessage(
Services.ppmm.getChildAt(0),
messageName,
data,
{
sender,
recipient,
responseType,
}
);
let result = undefined;
let failures = 0;
let tryPromise = async promise => {
try {
let res = await promise;
if (result === undefined) {
result = res;
}
} catch (e) {
if (e.result === MessageChannel.RESULT_NO_RESPONSE) {
// Ignore.
} else if (e.result === MessageChannel.RESULT_NO_HANDLER) {
failures++;
} else {
throw e;
}
}
};
await Promise.all([tryPromise(promise1), tryPromise(promise2)]);
if (failures == 2) {
return Promise.reject(noHandlerError);
}
return result;
}, },
/** /**
@ -703,11 +661,7 @@ GlobalManager = {
_onExtensionBrowser(type, browser, additionalData = {}) { _onExtensionBrowser(type, browser, additionalData = {}) {
browser.messageManager.loadFrameScript( browser.messageManager.loadFrameScript(
`data:, "resource://gre/modules/onExtensionBrowser.js",
Components.utils.import("resource://gre/modules/Services.jsm");
Services.obs.notifyObservers(this, "tab-content-frameloader-created", "");
`,
false, false,
true true
); );
@ -846,14 +800,14 @@ class ExtensionPageContextParent extends ProxyContextParent {
} }
// The window that contains this context. This may change due to moving tabs. // The window that contains this context. This may change due to moving tabs.
get xulWindow() { get appWindow() {
let win = this.xulBrowser.ownerGlobal; let win = this.xulBrowser.ownerGlobal;
return win.docShell.rootTreeItem.domWindow; return win.docShell.rootTreeItem.domWindow;
} }
get currentWindow() { get currentWindow() {
if (this.viewType !== "background") { if (this.viewType !== "background") {
return this.xulWindow; return this.appWindow;
} }
} }
@ -1283,7 +1237,7 @@ class HiddenXULWindow {
} }
/** /**
* Private helper that create a XULDocument in a windowless browser. * Private helper that create a HTMLDocument in a windowless browser.
* *
* @returns {Promise<void>} * @returns {Promise<void>}
* A promise which resolves when the windowless browser is ready. * A promise which resolves when the windowless browser is ready.
@ -1322,7 +1276,7 @@ class HiddenXULWindow {
triggeringPrincipal: system, triggeringPrincipal: system,
}; };
chromeShell.loadURI( chromeShell.loadURI(
"chrome://extensions/content/dummy.xul", "chrome://extensions/content/dummy.xhtml",
loadURIOptions loadURIOptions
); );

View File

@ -119,7 +119,7 @@ var ExtensionPermissions = {
} }
} }
if (added.permissions.length > 0 || added.origins.length > 0) { if (added.permissions.length || added.origins.length) {
await this._update(extensionId, { permissions, origins }); await this._update(extensionId, { permissions, origins });
if (emitter) { if (emitter) {
emitter.emit("add-permissions", added); emitter.emit("add-permissions", added);
@ -158,7 +158,7 @@ var ExtensionPermissions = {
} }
} }
if (removed.permissions.length > 0 || removed.origins.length > 0) { if (removed.permissions.length || removed.origins.length) {
await this._update(extensionId, { permissions, origins }); await this._update(extensionId, { permissions, origins });
if (emitter) { if (emitter) {
emitter.emit("remove-permissions", removed); emitter.emit("remove-permissions", removed);

View File

@ -45,11 +45,14 @@ using dom::ContentFrameMessageManager;
using dom::Document; using dom::Document;
using dom::Promise; using dom::Promise;
#define BASE_CSP_PREF "extensions.webextensions.base-content-security-policy"
#define DEFAULT_BASE_CSP \ #define DEFAULT_BASE_CSP \
"script-src 'self' https://* moz-extension: blob: filesystem: " \ "script-src 'self' https://* moz-extension: blob: filesystem: " \
"'unsafe-eval' 'unsafe-inline'; " \ "'unsafe-eval' 'unsafe-inline'; " \
"object-src 'self' https://* moz-extension: blob: filesystem:;" "object-src 'self' https://* moz-extension: blob: filesystem:;"
#define DEFAULT_CSP_PREF \
"extensions.webextensions.default-content-security-policy"
#define DEFAULT_DEFAULT_CSP "script-src 'self'; object-src 'self';" #define DEFAULT_DEFAULT_CSP "script-src 'self'; object-src 'self';"
#define OBS_TOPIC_PRELOAD_SCRIPT "web-extension-preload-content-script" #define OBS_TOPIC_PRELOAD_SCRIPT "web-extension-preload-content-script"
@ -97,6 +100,9 @@ ExtensionPolicyService::ExtensionPolicyService() {
Preferences::AddBoolVarCache(&sRemoteExtensions, Preferences::AddBoolVarCache(&sRemoteExtensions,
"extensions.webextensions.remote", false); "extensions.webextensions.remote", false);
mBaseCSP.SetIsVoid(true);
mDefaultCSP.SetIsVoid(true);
RegisterObservers(); RegisterObservers();
} }
@ -178,26 +184,6 @@ bool ExtensionPolicyService::UnregisterObserver(DocumentObserver& aObserver) {
return true; return true;
} }
void ExtensionPolicyService::BaseCSP(nsAString& aBaseCSP) const {
nsresult rv;
rv = Preferences::GetString(
"extensions.webextensions.base-content-security-policy", aBaseCSP);
if (NS_FAILED(rv)) {
aBaseCSP.AssignLiteral(DEFAULT_BASE_CSP);
}
}
void ExtensionPolicyService::DefaultCSP(nsAString& aDefaultCSP) const {
nsresult rv;
rv = Preferences::GetString(
"extensions.webextensions.default-content-security-policy", aDefaultCSP);
if (NS_FAILED(rv)) {
aDefaultCSP.AssignLiteral(DEFAULT_DEFAULT_CSP);
}
}
/***************************************************************************** /*****************************************************************************
* nsIMemoryReporter * nsIMemoryReporter
*****************************************************************************/ *****************************************************************************/
@ -243,6 +229,9 @@ void ExtensionPolicyService::RegisterObservers() {
if (XRE_IsContentProcess()) { if (XRE_IsContentProcess()) {
mObs->AddObserver(this, "http-on-opening-request", false); mObs->AddObserver(this, "http-on-opening-request", false);
} }
Preferences::AddStrongObserver(this, BASE_CSP_PREF);
Preferences::AddStrongObserver(this, DEFAULT_CSP_PREF);
} }
void ExtensionPolicyService::UnregisterObservers() { void ExtensionPolicyService::UnregisterObservers() {
@ -251,6 +240,9 @@ void ExtensionPolicyService::UnregisterObservers() {
if (XRE_IsContentProcess()) { if (XRE_IsContentProcess()) {
mObs->RemoveObserver(this, "http-on-opening-request"); mObs->RemoveObserver(this, "http-on-opening-request");
} }
Preferences::RemoveObserver(this, BASE_CSP_PREF);
Preferences::RemoveObserver(this, DEFAULT_CSP_PREF);
} }
nsresult ExtensionPolicyService::Observe(nsISupports* aSubject, nsresult ExtensionPolicyService::Observe(nsISupports* aSubject,
@ -273,6 +265,14 @@ nsresult ExtensionPolicyService::Observe(nsISupports* aSubject,
mMessageManagers.PutEntry(mm); mMessageManagers.PutEntry(mm);
mm->AddSystemEventListener(NS_LITERAL_STRING("unload"), this, false, false); mm->AddSystemEventListener(NS_LITERAL_STRING("unload"), this, false, false);
} else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
const nsCString converted = NS_ConvertUTF16toUTF8(aData);
const char* pref = converted.get();
if (!strcmp(pref, BASE_CSP_PREF)) {
mBaseCSP.SetIsVoid(true);
} else if (!strcmp(pref, DEFAULT_CSP_PREF)) {
mDefaultCSP.SetIsVoid(true);
}
} }
return NS_OK; return NS_OK;
} }
@ -523,19 +523,44 @@ void ExtensionPolicyService::CheckContentScripts(const DocInfo& aDocInfo,
*****************************************************************************/ *****************************************************************************/
nsresult ExtensionPolicyService::GetBaseCSP(nsAString& aBaseCSP) { nsresult ExtensionPolicyService::GetBaseCSP(nsAString& aBaseCSP) {
BaseCSP(aBaseCSP); if (mBaseCSP.IsVoid()) {
nsresult rv = Preferences::GetString(BASE_CSP_PREF, mBaseCSP);
if (NS_FAILED(rv)) {
mBaseCSP.AssignLiteral(DEFAULT_BASE_CSP);
}
mBaseCSP.SetIsVoid(false);
}
aBaseCSP.Assign(mBaseCSP);
return NS_OK; return NS_OK;
} }
nsresult ExtensionPolicyService::GetDefaultCSP(nsAString& aDefaultCSP) { nsresult ExtensionPolicyService::GetDefaultCSP(nsAString& aDefaultCSP) {
DefaultCSP(aDefaultCSP); if (mDefaultCSP.IsVoid()) {
nsresult rv = Preferences::GetString(DEFAULT_CSP_PREF, mDefaultCSP);
if (NS_FAILED(rv)) {
mDefaultCSP.AssignLiteral(DEFAULT_DEFAULT_CSP);
}
mDefaultCSP.SetIsVoid(false);
}
aDefaultCSP.Assign(mDefaultCSP);
return NS_OK; return NS_OK;
} }
nsresult ExtensionPolicyService::GetAddonCSP(const nsAString& aAddonId, nsresult ExtensionPolicyService::GetExtensionPageCSP(const nsAString& aAddonId,
nsAString& aResult) { nsAString& aResult) {
if (WebExtensionPolicy* policy = GetByID(aAddonId)) { if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
policy->GetContentSecurityPolicy(aResult); policy->GetExtensionPageCSP(aResult);
return NS_OK;
}
return NS_ERROR_INVALID_ARG;
}
nsresult ExtensionPolicyService::GetContentScriptCSP(const nsAString& aAddonId,
nsAString& aResult) {
if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
policy->GetContentScriptCSP(aResult);
return NS_OK; return NS_OK;
} }
return NS_ERROR_INVALID_ARG; return NS_ERROR_INVALID_ARG;

View File

@ -83,9 +83,6 @@ class ExtensionPolicyService final : public nsIAddonPolicyService,
bool RegisterObserver(extensions::DocumentObserver& aPolicy); bool RegisterObserver(extensions::DocumentObserver& aPolicy);
bool UnregisterObserver(extensions::DocumentObserver& aPolicy); bool UnregisterObserver(extensions::DocumentObserver& aPolicy);
void BaseCSP(nsAString& aDefaultCSP) const;
void DefaultCSP(nsAString& aDefaultCSP) const;
bool UseRemoteExtensions() const; bool UseRemoteExtensions() const;
bool IsExtensionProcess() const; bool IsExtensionProcess() const;
@ -126,6 +123,9 @@ class ExtensionPolicyService final : public nsIAddonPolicyService,
nsCOMPtr<nsIObserverService> mObs; nsCOMPtr<nsIObserverService> mObs;
static bool sRemoteExtensions; static bool sRemoteExtensions;
nsString mBaseCSP;
nsString mDefaultCSP;
}; };
} // namespace mozilla } // namespace mozilla

View File

@ -208,7 +208,8 @@ ExtensionManager = {
allowedOrigins: extension.whiteListedHosts, allowedOrigins: extension.whiteListedHosts,
webAccessibleResources: extension.webAccessibleResources, webAccessibleResources: extension.webAccessibleResources,
contentSecurityPolicy: extension.contentSecurityPolicy, extensionPageCSP: extension.extensionPageCSP,
contentScriptCSP: extension.contentScriptCSP,
localizeCallback, localizeCallback,

View File

@ -15,7 +15,6 @@ const { IndexedDB } = ChromeUtils.import(
XPCOMUtils.defineLazyModuleGetters(this, { XPCOMUtils.defineLazyModuleGetters(this, {
ExtensionStorage: "resource://gre/modules/ExtensionStorage.jsm", ExtensionStorage: "resource://gre/modules/ExtensionStorage.jsm",
getTrimmedString: "resource://gre/modules/ExtensionTelemetry.jsm",
Services: "resource://gre/modules/Services.jsm", Services: "resource://gre/modules/Services.jsm",
OS: "resource://gre/modules/osfile.jsm", OS: "resource://gre/modules/osfile.jsm",
}); });
@ -665,6 +664,9 @@ this.ExtensionStorageIDB = {
promise = migrateJSONFileData(extension, storagePrincipal) promise = migrateJSONFileData(extension, storagePrincipal)
.then(() => { .then(() => {
extension.setSharedData("storageIDBBackend", true);
extension.setSharedData("storageIDBPrincipal", storagePrincipal);
Services.ppmm.sharedData.flush();
return { return {
backendEnabled: true, backendEnabled: true,
storagePrincipal: serializedPrincipal, storagePrincipal: serializedPrincipal,
@ -684,6 +686,9 @@ this.ExtensionStorageIDB = {
"JSONFile backend is being kept enabled by an unexpected " + "JSONFile backend is being kept enabled by an unexpected " +
`IDBBackend failure: ${err.message}::${err.stack}` `IDBBackend failure: ${err.message}::${err.stack}`
); );
extension.setSharedData("storageIDBBackend", false);
Services.ppmm.sharedData.flush();
return { backendEnabled: false }; return { backendEnabled: false };
}); });
} }

View File

@ -1,186 +0,0 @@
/* 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 EXPORTED_SYMBOLS = ["ExtensionTelemetry", "getTrimmedString"];
ChromeUtils.defineModuleGetter(
this,
"Services",
"resource://gre/modules/Services.jsm"
);
// Map of the base histogram ids for the metrics recorded for the extensions.
const histograms = {
extensionStartup: "WEBEXT_EXTENSION_STARTUP_MS",
backgroundPageLoad: "WEBEXT_BACKGROUND_PAGE_LOAD_MS",
browserActionPopupOpen: "WEBEXT_BROWSERACTION_POPUP_OPEN_MS",
browserActionPreloadResult: "WEBEXT_BROWSERACTION_POPUP_PRELOAD_RESULT_COUNT",
contentScriptInjection: "WEBEXT_CONTENT_SCRIPT_INJECTION_MS",
pageActionPopupOpen: "WEBEXT_PAGEACTION_POPUP_OPEN_MS",
storageLocalGetJSON: "WEBEXT_STORAGE_LOCAL_GET_MS",
storageLocalSetJSON: "WEBEXT_STORAGE_LOCAL_SET_MS",
storageLocalGetIDB: "WEBEXT_STORAGE_LOCAL_IDB_GET_MS",
storageLocalSetIDB: "WEBEXT_STORAGE_LOCAL_IDB_SET_MS",
userScriptInjection: "WEBEXT_USER_SCRIPT_INJECTION_MS",
};
/**
* Get a trimmed version of the given string if it is longer than 80 chars (used in telemetry
* when a string may be longer than allowed).
*
* @param {string} str
* The original string content.
*
* @returns {string}
* The trimmed version of the string when longer than 80 chars, or the given string
* unmodified otherwise.
*/
function getTrimmedString(str) {
if (str.length <= 80) {
return str;
}
const length = str.length;
// Trim the string to prevent a flood of warnings messages logged internally by recordEvent,
// the trimmed version is going to be composed by the first 40 chars and the last 37 and 3 dots
// that joins the two parts, to visually indicate that the string has been trimmed.
return `${str.slice(0, 40)}...${str.slice(length - 37, length)}`;
}
/**
* This is a internal helper object which contains a collection of helpers used to make it easier
* to collect extension telemetry (in both the general histogram and in the one keyed by addon id).
*
* This helper object is not exported from ExtensionUtils, it is used by the ExtensionTelemetry
* Proxy which is exported and used by the callers to record telemetry data for one of the
* supported metrics.
*/
class ExtensionTelemetryMetric {
constructor(metric) {
this.metric = metric;
}
// Stopwatch methods.
stopwatchStart(extension, obj = extension) {
this._wrappedStopwatchMethod("start", this.metric, extension, obj);
}
stopwatchFinish(extension, obj = extension) {
this._wrappedStopwatchMethod("finish", this.metric, extension, obj);
}
stopwatchCancel(extension, obj = extension) {
this._wrappedStopwatchMethod("cancel", this.metric, extension, obj);
}
// Histogram counters methods.
histogramAdd(opts) {
this._histogramAdd(this.metric, opts);
}
/**
* Wraps a call to a TelemetryStopwatch method for a given metric and extension.
*
* @param {string} method
* The stopwatch method to call ("start", "finish" or "cancel").
* @param {string} metric
* The stopwatch metric to record (used to retrieve the base histogram id from the _histogram object).
* @param {Extension | BrowserExtensionContent} extension
* The extension to record the telemetry for.
* @param {any | undefined} [obj = extension]
* An optional telemetry stopwatch object (which defaults to the extension parameter when missing).
*/
_wrappedStopwatchMethod(method, metric, extension, obj = extension) {
if (!extension) {
throw new Error(`Mandatory extension parameter is undefined`);
}
const baseId = histograms[metric];
if (!baseId) {
throw new Error(`Unknown metric ${metric}`);
}
// Record metric in the general histogram.
TelemetryStopwatch[method](baseId, obj);
// Record metric in the histogram keyed by addon id.
let extensionId = getTrimmedString(extension.id);
TelemetryStopwatch[`${method}Keyed`](
`${baseId}_BY_ADDONID`,
extensionId,
obj
);
}
/**
* Record a telemetry category and/or value for a given metric.
*
* @param {string} metric
* The metric to record (used to retrieve the base histogram id from the _histogram object).
* @param {Object} options
* @param {Extension | BrowserExtensionContent} options.extension
* The extension to record the telemetry for.
* @param {string | undefined} [options.category]
* An optional histogram category.
* @param {number | undefined} [options.value]
* An optional value to record.
*/
_histogramAdd(metric, { category, extension, value }) {
if (!extension) {
throw new Error(`Mandatory extension parameter is undefined`);
}
const baseId = histograms[metric];
if (!baseId) {
throw new Error(`Unknown metric ${metric}`);
}
const histogram = Services.telemetry.getHistogramById(baseId);
if (typeof category === "string") {
histogram.add(category, value);
} else {
histogram.add(value);
}
const keyedHistogram = Services.telemetry.getKeyedHistogramById(
`${baseId}_BY_ADDONID`
);
const extensionId = getTrimmedString(extension.id);
if (typeof category === "string") {
keyedHistogram.add(extensionId, category, value);
} else {
keyedHistogram.add(extensionId, value);
}
}
}
// Cache of the ExtensionTelemetryMetric instances that has been lazily created by the
// Extension Telemetry Proxy.
const metricsCache = new Map();
/**
* This proxy object provides the telemetry helpers for the currently supported metrics (the ones listed in
* ExtensionTelemetryHelpers._histograms), the telemetry helpers for a particular metric are lazily created
* when the related property is being accessed on this object for the first time, e.g.:
*
* ExtensionTelemetry.extensionStartup.stopwatchStart(extension);
* ExtensionTelemetry.browserActionPreloadResult.histogramAdd({category: "Shown", extension});
*/
var ExtensionTelemetry = new Proxy(metricsCache, {
get(target, prop, receiver) {
if (!(prop in histograms)) {
throw new Error(`Unknown metric ${prop}`);
}
// Lazily create and cache the metric result object.
if (!target.has(prop)) {
target.set(prop, new ExtensionTelemetryMetric(prop));
}
return target.get(prop);
},
});

View File

@ -134,6 +134,9 @@ function frameScript() {
let kungFuDeathGrip = new Set(); let kungFuDeathGrip = new Set();
function promiseBrowserLoaded(browser, url, redirectUrl) { function promiseBrowserLoaded(browser, url, redirectUrl) {
url = url && Services.io.newURI(url);
redirectUrl = redirectUrl && Services.io.newURI(redirectUrl);
return new Promise(resolve => { return new Promise(resolve => {
const listener = { const listener = {
QueryInterface: ChromeUtils.generateQI([ QueryInterface: ChromeUtils.generateQI([
@ -144,12 +147,12 @@ function promiseBrowserLoaded(browser, url, redirectUrl) {
onStateChange(webProgress, request, stateFlags, statusCode) { onStateChange(webProgress, request, stateFlags, statusCode) {
request.QueryInterface(Ci.nsIChannel); request.QueryInterface(Ci.nsIChannel);
let requestUrl = request.originalURI let requestURI =
? request.originalURI.spec request.originalURI ||
: webProgress.DOMWindow.location.href; webProgress.DOMWindow.document.documentURIObject;
if ( if (
webProgress.isTopLevel && webProgress.isTopLevel &&
(requestUrl === url || requestUrl === redirectUrl) && (url?.equals(requestURI) || redirectUrl?.equals(requestURI)) &&
stateFlags & Ci.nsIWebProgressListener.STATE_STOP stateFlags & Ci.nsIWebProgressListener.STATE_STOP
) { ) {
resolve(); resolve();
@ -207,7 +210,7 @@ class ContentPage {
triggeringPrincipal: system, triggeringPrincipal: system,
}; };
chromeShell.loadURI( chromeShell.loadURI(
"chrome://extensions/content/dummy.xul", "chrome://extensions/content/dummy.xhtml",
loadURIOptions loadURIOptions
); );
@ -218,7 +221,7 @@ class ContentPage {
let chromeDoc = await promiseDocumentLoaded(chromeShell.document); let chromeDoc = await promiseDocumentLoaded(chromeShell.document);
let browser = chromeDoc.createElement("browser"); let browser = chromeDoc.createXULElement("browser");
browser.setAttribute("type", "content"); browser.setAttribute("type", "content");
browser.setAttribute("disableglobalhistory", "true"); browser.setAttribute("disableglobalhistory", "true");
if (this.userContextId) { if (this.userContextId) {
@ -938,6 +941,16 @@ var ExtensionTestUtils = {
return new ExternallyInstalledWrapper(this.currentScope, id); return new ExternallyInstalledWrapper(this.currentScope, id);
}, },
failOnSchemaWarnings(warningsAsErrors = true) {
let prefName = "extensions.webextensions.warnings-as-errors";
Services.prefs.setBoolPref(prefName, warningsAsErrors);
if (!warningsAsErrors) {
this.currentScope.registerCleanupFunction(() => {
Services.prefs.setBoolPref(prefName, true);
});
}
},
get remoteContentScripts() { get remoteContentScripts() {
return REMOTE_CONTENT_SCRIPTS; return REMOTE_CONTENT_SCRIPTS;
}, },

View File

@ -322,15 +322,15 @@ void MatchPattern::Init(JSContext* aCx, const nsAString& aPattern,
if (host.EqualsLiteral("*")) { if (host.EqualsLiteral("*")) {
mMatchSubdomain = true; mMatchSubdomain = true;
} else if (StringHead(host, 2).EqualsLiteral("*.")) { } else if (StringHead(host, 2).EqualsLiteral("*.")) {
mDomain = NS_ConvertUTF16toUTF8(Substring(host, 2)); CopyUTF16toUTF8(Substring(host, 2), mDomain);
mMatchSubdomain = true; mMatchSubdomain = true;
} else if (host.Length() > 1 && host[0] == '[' && } else if (host.Length() > 1 && host[0] == '[' &&
host[host.Length() - 1] == ']') { host[host.Length() - 1] == ']') {
// This is an IPv6 literal, we drop the enclosing `[]` to be // This is an IPv6 literal, we drop the enclosing `[]` to be
// consistent with nsIURI. // consistent with nsIURI.
mDomain = NS_ConvertUTF16toUTF8(Substring(host, 1, host.Length() - 2)); CopyUTF16toUTF8(Substring(host, 1, host.Length() - 2), mDomain);
} else { } else {
mDomain = NS_ConvertUTF16toUTF8(host); CopyUTF16toUTF8(host, mDomain);
} }
} }

View File

@ -21,7 +21,7 @@ class MatchURLFilters {
throw new TypeError("filters should be an array"); throw new TypeError("filters should be an array");
} }
if (filters.length == 0) { if (!filters.length) {
throw new Error("filters array should not be empty"); throw new Error("filters array should not be empty");
} }

View File

@ -890,7 +890,7 @@ this.MessageChannel = {
// At least one handler is required for all response types but // At least one handler is required for all response types but
// RESPONSE_ALL. // RESPONSE_ALL.
if (handlers.length == 0 && responseType != this.RESPONSE_ALL) { if (!handlers.length && responseType != this.RESPONSE_ALL) {
return Promise.reject({ return Promise.reject({
result: MessageChannel.RESULT_NO_HANDLER, result: MessageChannel.RESULT_NO_HANDLER,
message: "No matching message handler", message: "No matching message handler",
@ -925,7 +925,7 @@ this.MessageChannel = {
switch (responseType) { switch (responseType) {
case this.RESPONSE_FIRST: case this.RESPONSE_FIRST:
if (responses.length == 0) { if (!responses.length) {
return Promise.reject({ return Promise.reject({
result: MessageChannel.RESULT_NO_RESPONSE, result: MessageChannel.RESULT_NO_RESPONSE,
message: "No handler returned a response", message: "No handler returned a response",

View File

@ -196,7 +196,7 @@ var NativeApp = class extends EventEmitter {
} }
_startWrite() { _startWrite() {
if (this.sendQueue.length == 0) { if (!this.sendQueue.length) {
return; return;
} }
@ -228,7 +228,7 @@ var NativeApp = class extends EventEmitter {
let partial = ""; let partial = "";
while (true) { while (true) {
let data = await proc.stderr.readString(); let data = await proc.stderr.readString();
if (data.length == 0) { if (!data.length) {
// We have hit EOF, just stop reading // We have hit EOF, just stop reading
if (partial) { if (partial) {
Services.console.logStringMessage( Services.console.logStringMessage(

View File

@ -3,36 +3,22 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict"; "use strict";
var EXPORTED_SYMBOLS = ["ProxyScriptContext", "ProxyChannelFilter"]; var EXPORTED_SYMBOLS = ["ProxyChannelFilter"];
/* exported ProxyScriptContext, ProxyChannelFilter */ /* exported ProxyChannelFilter */
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import( const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm" "resource://gre/modules/XPCOMUtils.jsm"
); );
const { ExtensionCommon } = ChromeUtils.import(
"resource://gre/modules/ExtensionCommon.jsm"
);
const { ExtensionUtils } = ChromeUtils.import( const { ExtensionUtils } = ChromeUtils.import(
"resource://gre/modules/ExtensionUtils.jsm" "resource://gre/modules/ExtensionUtils.jsm"
); );
ChromeUtils.defineModuleGetter(
this,
"ExtensionChild",
"resource://gre/modules/ExtensionChild.jsm"
);
ChromeUtils.defineModuleGetter( ChromeUtils.defineModuleGetter(
this, this,
"ExtensionParent", "ExtensionParent",
"resource://gre/modules/ExtensionParent.jsm" "resource://gre/modules/ExtensionParent.jsm"
); );
ChromeUtils.defineModuleGetter(
this,
"Schemas",
"resource://gre/modules/Schemas.jsm"
);
XPCOMUtils.defineLazyServiceGetter( XPCOMUtils.defineLazyServiceGetter(
this, this,
"ProxyService", "ProxyService",
@ -47,8 +33,6 @@ XPCOMUtils.defineLazyGetter(this, "getCookieStoreIdForOriginAttributes", () => {
return ExtensionParent.apiManager.global.getCookieStoreIdForOriginAttributes; return ExtensionParent.apiManager.global.getCookieStoreIdForOriginAttributes;
}); });
const CATEGORY_EXTENSION_SCRIPTS_CONTENT = "webextension-scripts-content";
// DNS is resolved on the SOCKS proxy server. // DNS is resolved on the SOCKS proxy server.
const { TRANSPARENT_PROXY_RESOLVES_HOST } = Ci.nsIProxyInfo; const { TRANSPARENT_PROXY_RESOLVES_HOST } = Ci.nsIProxyInfo;
@ -57,14 +41,6 @@ const PROXY_TIMEOUT_SEC = 10;
const { ExtensionError } = ExtensionUtils; const { ExtensionError } = ExtensionUtils;
const {
BaseContext,
CanOfAPIs,
LocalAPIImplementation,
SchemaAPIManager,
defineLazyGetter,
} = ExtensionCommon;
const PROXY_TYPES = Object.freeze({ const PROXY_TYPES = Object.freeze({
DIRECT: "direct", DIRECT: "direct",
HTTPS: "https", HTTPS: "https",
@ -233,7 +209,7 @@ const ProxyInfoData = {
proxyAuthorizationHeader, proxyAuthorizationHeader,
connectionIsolationKey, connectionIsolationKey,
} = ProxyInfoData.validate(proxyData); } = ProxyInfoData.validate(proxyData);
if (type === PROXY_TYPES.DIRECT) { if (type === PROXY_TYPES.DIRECT && defaultProxyInfo) {
return defaultProxyInfo; return defaultProxyInfo;
} }
let failoverProxy = this.createProxyInfoFromData( let failoverProxy = this.createProxyInfoFromData(
@ -267,93 +243,6 @@ const ProxyInfoData = {
failoverProxy failoverProxy
); );
}, },
/**
* Creates a new proxy info data object using the return value of FindProxyForURL.
*
* @param {Array<string>} rule A single proxy rule returned by FindProxyForURL.
* (e.g. "PROXY 1.2.3.4:8080", "SOCKS 1.1.1.1:9090" or "DIRECT")
* @returns {nsIProxyInfo} The proxy info to apply for the given URI.
*/
parseProxyInfoDataFromPAC(rule) {
if (!rule) {
throw new ExtensionError("ProxyInfoData: Missing Proxy Rule");
}
let parts = rule.toLowerCase().split(/\s+/);
if (!parts[0] || parts.length > 2) {
throw new ExtensionError(
`ProxyInfoData: Invalid arguments passed for proxy rule: "${rule}"`
);
}
let type = parts[0];
let [host, port] = parts.length > 1 ? parts[1].split(":") : [];
switch (PROXY_TYPES[type.toUpperCase()]) {
case PROXY_TYPES.HTTP:
case PROXY_TYPES.HTTPS:
case PROXY_TYPES.SOCKS:
case PROXY_TYPES.SOCKS4:
if (!host || !port) {
throw new ExtensionError(
`ProxyInfoData: Invalid host or port from proxy rule: "${rule}"`
);
}
return { type, host, port };
case PROXY_TYPES.DIRECT:
if (host || port) {
throw new ExtensionError(
`ProxyInfoData: Invalid argument for proxy type: "${type}"`
);
}
return { type };
default:
throw new ExtensionError(
`ProxyInfoData: Unrecognized proxy type: "${type}"`
);
}
},
proxyInfoFromProxyData(context, proxyData, defaultProxyInfo) {
switch (typeof proxyData) {
case "string":
let proxyRules = [];
try {
for (let result of proxyData.split(";")) {
proxyRules.push(
ProxyInfoData.parseProxyInfoDataFromPAC(result.trim())
);
}
} catch (e) {
// If we have valid proxies already, lets use them and just emit
// errors for the failovers.
if (proxyRules.length === 0) {
throw e;
}
let error = context.normalizeError(e);
context.extension.emit("proxy-error", {
message: error.message,
fileName: error.fileName,
lineNumber: error.lineNumber,
stack: error.stack,
});
}
proxyData = proxyRules;
// fall through
case "object":
if (Array.isArray(proxyData) && proxyData.length > 0) {
return ProxyInfoData.createProxyInfoFromData(
proxyData,
defaultProxyInfo
);
}
// Not an array, fall through to error.
default:
throw new ExtensionError(
"ProxyInfoData: proxyData must be a string or array of objects"
);
}
},
}; };
function normalizeFilter(filter) { function normalizeFilter(filter) {
@ -502,195 +391,3 @@ class ProxyChannelFilter {
ProxyService.unregisterFilter(this); ProxyService.unregisterFilter(this);
} }
} }
class ProxyScriptContext extends BaseContext {
constructor(extension, url, contextInfo = {}) {
super("proxy_script", extension);
this.contextInfo = contextInfo;
this.extension = extension;
this.messageManager = Services.cpmm;
this.sandbox = Cu.Sandbox(this.extension.principal, {
sandboxName: `Extension Proxy Script (${
extension.policy.debugName
}): ${url}`,
metadata: { addonID: extension.id },
});
this.url = url;
this.FindProxyForURL = null;
}
/**
* Loads and validates a proxy script into the sandbox, and then
* registers a new proxy filter for the context.
*
* @returns {boolean} true if load succeeded; false otherwise.
*/
load() {
Schemas.exportLazyGetter(this.sandbox, "browser", () => this.browserObj);
try {
Services.scriptloader.loadSubScript(this.url, this.sandbox);
} catch (error) {
this.extension.emit("proxy-error", {
message: this.normalizeError(error).message,
});
return false;
}
this.FindProxyForURL = Cu.unwaiveXrays(this.sandbox.FindProxyForURL);
if (typeof this.FindProxyForURL !== "function") {
this.extension.emit("proxy-error", {
message: "The proxy script must define FindProxyForURL as a function",
});
return false;
}
ProxyService.registerChannelFilter(
this /* nsIProtocolProxyChannelFilter aFilter */,
0 /* unsigned long aPosition */
);
return true;
}
get principal() {
return this.extension.principal;
}
get cloneScope() {
return this.sandbox;
}
/**
* This method (which is required by the nsIProtocolProxyService interface)
* is called to apply proxy filter rules for the given URI and proxy object
* (or list of proxy objects).
*
* @param {nsIProtocolProxyService} service A reference to the Protocol Proxy Service.
* @param {nsIChannel} channel The channel for which these proxy settings apply.
* @param {nsIProxyInfo} defaultProxyInfo The proxy (or list of proxies) that
* would be used by default for the given URI. This may be null.
* @param {nsIProxyProtocolFilterResult} proxyFilter to call
on with the proxy info to apply for the given URI.
*/
applyFilter(service, channel, defaultProxyInfo, proxyFilter) {
let proxyInfo;
try {
let wrapper = ChannelWrapper.get(channel);
if (
this.extension.policy.privateBrowsingAllowed ||
wrapper.loadInfo.originAttributes.privateBrowsingId == 0
) {
let uri = wrapper.finalURI;
// TODO Bug 1337001 - provide path and query components to non-https URLs.
let ret = this.FindProxyForURL(uri.prePath, uri.host, this.contextInfo);
proxyInfo = ProxyInfoData.proxyInfoFromProxyData(
this,
ret,
defaultProxyInfo
);
}
} catch (e) {
let error = this.normalizeError(e);
this.extension.emit("proxy-error", {
message: error.message,
fileName: error.fileName,
lineNumber: error.lineNumber,
stack: error.stack,
});
} finally {
// FindProxyForURL may return nothing, null, or proxyInfo.
proxyFilter.onProxyFilterResult(
proxyInfo !== undefined ? proxyInfo : defaultProxyInfo
);
}
}
/**
* Unloads the proxy filter and shuts down the sandbox.
*/
unload() {
super.unload();
ProxyService.unregisterFilter(this);
Cu.nukeSandbox(this.sandbox);
this.sandbox = null;
}
}
class ProxyScriptAPIManager extends SchemaAPIManager {
constructor() {
super("proxy", Schemas);
this.initialized = false;
}
lazyInit() {
if (!this.initialized) {
this.initGlobal();
let entries = Services.catMan.enumerateCategory(
CATEGORY_EXTENSION_SCRIPTS_CONTENT
);
for (let { value } of entries) {
this.loadScript(value);
}
this.initialized = true;
}
}
}
class ProxyScriptInjectionContext {
constructor(context, apiCan) {
this.context = context;
this.localAPIs = apiCan.root;
this.apiCan = apiCan;
}
shouldInject(namespace, name, allowedContexts) {
if (this.context.envType !== "proxy_script") {
throw new Error(`Unexpected context type "${this.context.envType}"`);
}
// Do not generate proxy script APIs unless explicitly allowed.
return allowedContexts.includes("proxy");
}
getImplementation(namespace, name) {
this.apiCan.findAPIPath(`${namespace}.${name}`);
let obj = this.apiCan.findAPIPath(namespace);
if (obj && name in obj) {
return new LocalAPIImplementation(obj, name, this.context);
}
}
get cloneScope() {
return this.context.cloneScope;
}
get principal() {
return this.context.principal;
}
}
defineLazyGetter(ProxyScriptContext.prototype, "messenger", function() {
let sender = { id: this.extension.id, frameId: this.frameId, url: this.url };
let filter = { extensionId: this.extension.id, toProxyScript: true };
return new ExtensionChild.Messenger(
this,
[this.messageManager],
sender,
filter
);
});
let proxyScriptAPIManager = new ProxyScriptAPIManager();
defineLazyGetter(ProxyScriptContext.prototype, "browserObj", function() {
let localAPIs = {};
let can = new CanOfAPIs(this, proxyScriptAPIManager, localAPIs);
proxyScriptAPIManager.lazyInit();
let browserObj = Cu.createObjectIn(this.sandbox);
let injectionContext = new ProxyScriptInjectionContext(this, can);
proxyScriptAPIManager.schema.inject(browserObj, injectionContext);
return browserObj;
});

View File

@ -1047,6 +1047,10 @@ const FORMATS = {
contentSecurityPolicy(string, context) { contentSecurityPolicy(string, context) {
let error = contentPolicyService.validateAddonCSP(string); let error = contentPolicyService.validateAddonCSP(string);
if (error != null) { if (error != null) {
// The SyntaxError raised below is not reported as part of the "choices" error message,
// we log the CSP validation error explicitly here to make it easier for the addon developers
// to see and fix the extension CSP.
context.logError(`Error processing ${context.currentTarget}: ${error}`);
throw new SyntaxError(error); throw new SyntaxError(error);
} }
return string; return string;
@ -3151,7 +3155,7 @@ class SchemaRoots extends Namespaces {
return results[0]; return results[0];
} }
if (results.length > 0) { if (results.length) {
return new Namespaces(this.root, name, name.split("."), results); return new Namespaces(this.root, name, name.split("."), results);
} }
return null; return null;

View File

@ -127,7 +127,8 @@ WebExtensionPolicy::WebExtensionPolicy(GlobalObject& aGlobal,
: mId(NS_AtomizeMainThread(aInit.mId)), : mId(NS_AtomizeMainThread(aInit.mId)),
mHostname(aInit.mMozExtensionHostname), mHostname(aInit.mMozExtensionHostname),
mName(aInit.mName), mName(aInit.mName),
mContentSecurityPolicy(aInit.mContentSecurityPolicy), mExtensionPageCSP(aInit.mExtensionPageCSP),
mContentScriptCSP(aInit.mContentScriptCSP),
mLocalizeCallback(aInit.mLocalizeCallback), mLocalizeCallback(aInit.mLocalizeCallback),
mIsPrivileged(aInit.mIsPrivileged), mIsPrivileged(aInit.mIsPrivileged),
mPermissions(new AtomSet(aInit.mPermissions)) { mPermissions(new AtomSet(aInit.mPermissions)) {
@ -154,8 +155,12 @@ WebExtensionPolicy::WebExtensionPolicy(GlobalObject& aGlobal,
aInit.mBackgroundScripts.Value()); aInit.mBackgroundScripts.Value());
} }
if (mContentSecurityPolicy.IsVoid()) { if (mExtensionPageCSP.IsVoid()) {
EPS().DefaultCSP(mContentSecurityPolicy); EPS().GetDefaultCSP(mExtensionPageCSP);
}
if (mContentScriptCSP.IsVoid()) {
EPS().GetDefaultCSP(mContentScriptCSP);
} }
mContentScripts.SetCapacity(aInit.mContentScripts.Length()); mContentScripts.SetCapacity(aInit.mContentScripts.Length());

View File

@ -99,12 +99,11 @@ class WebExtensionPolicy final : public nsISupports,
const nsString& Name() const { return mName; } const nsString& Name() const { return mName; }
void GetName(nsAString& aName) const { aName = mName; } void GetName(nsAString& aName) const { aName = mName; }
const nsString& ContentSecurityPolicy() const { const nsString& ExtensionPageCSP() const { return mExtensionPageCSP; }
return mContentSecurityPolicy; void GetExtensionPageCSP(nsAString& aCSP) const { aCSP = mExtensionPageCSP; }
}
void GetContentSecurityPolicy(nsAString& aCSP) const { const nsString& ContentScriptCSP() const { return mContentScriptCSP; }
aCSP = mContentSecurityPolicy; void GetContentScriptCSP(nsAString& aCSP) const { aCSP = mContentScriptCSP; }
}
already_AddRefed<MatchPatternSet> AllowedOrigins() { already_AddRefed<MatchPatternSet> AllowedOrigins() {
return do_AddRef(mHostPermissions); return do_AddRef(mHostPermissions);
@ -176,7 +175,8 @@ class WebExtensionPolicy final : public nsISupports,
nsCOMPtr<nsIURI> mBaseURI; nsCOMPtr<nsIURI> mBaseURI;
nsString mName; nsString mName;
nsString mContentSecurityPolicy; nsString mExtensionPageCSP;
nsString mContentScriptCSP;
bool mActive = false; bool mActive = false;
bool mAllowPrivateBrowsingByDefault = true; bool mAllowPrivateBrowsingByDefault = true;

View File

@ -38,7 +38,6 @@ this.runtime = class extends ExtensionAPI {
} }
function checkOptions(options) { function checkOptions(options) {
let toProxyScript = false;
if (typeof options !== "object") { if (typeof options !== "object") {
return [ return [
false, false,
@ -47,21 +46,10 @@ this.runtime = class extends ExtensionAPI {
} }
for (let key of Object.keys(options)) { for (let key of Object.keys(options)) {
if (key === "toProxyScript") { return [false, `Unexpected property ${key}`];
let value = options[key];
if (typeof value !== "boolean") {
return [
false,
"runtime.sendMessage's options.toProxyScript argument is invalid",
];
}
toProxyScript = value;
} else {
return [false, `Unexpected property ${key}`];
}
} }
return [true, { toProxyScript }]; return [true, {}];
} }
if (!args.length) { if (!args.length) {

View File

@ -10,57 +10,26 @@ ChromeUtils.defineModuleGetter(
"ExtensionStorageIDB", "ExtensionStorageIDB",
"resource://gre/modules/ExtensionStorageIDB.jsm" "resource://gre/modules/ExtensionStorageIDB.jsm"
); );
ChromeUtils.defineModuleGetter(
this,
"ExtensionTelemetry",
"resource://gre/modules/ExtensionTelemetry.jsm"
);
ChromeUtils.defineModuleGetter( ChromeUtils.defineModuleGetter(
this, this,
"Services", "Services",
"resource://gre/modules/Services.jsm" "resource://gre/modules/Services.jsm"
); );
// Wrap a storage operation in a TelemetryStopWatch.
async function measureOp(telemetryMetric, extension, fn) {
const stopwatchKey = {};
telemetryMetric.stopwatchStart(extension, stopwatchKey);
try {
let result = await fn();
telemetryMetric.stopwatchFinish(extension, stopwatchKey);
return result;
} catch (err) {
telemetryMetric.stopwatchCancel(extension, stopwatchKey);
throw err;
}
}
this.storage = class extends ExtensionAPI { this.storage = class extends ExtensionAPI {
getLocalFileBackend(context, { deserialize, serialize }) { getLocalFileBackend(context, { deserialize, serialize }) {
return { return {
get(keys) { get(keys) {
return measureOp( return context.childManager
ExtensionTelemetry.storageLocalGetJSON, .callParentAsyncFunction("storage.local.JSONFileBackend.get", [
context.extension, serialize(keys),
() => { ])
return context.childManager .then(deserialize);
.callParentAsyncFunction("storage.local.JSONFileBackend.get", [
serialize(keys),
])
.then(deserialize);
}
);
}, },
set(items) { set(items) {
return measureOp( return context.childManager.callParentAsyncFunction(
ExtensionTelemetry.storageLocalSetJSON, "storage.local.JSONFileBackend.set",
context.extension, [serialize(items)]
() => {
return context.childManager.callParentAsyncFunction(
"storage.local.JSONFileBackend.set",
[serialize(items)]
);
}
); );
}, },
remove(keys) { remove(keys) {
@ -99,31 +68,19 @@ this.storage = class extends ExtensionAPI {
} }
return { return {
get(keys) { async get(keys) {
return measureOp( const db = await getDB();
ExtensionTelemetry.storageLocalGetIDB, return db.get(keys);
context.extension,
async () => {
const db = await getDB();
return db.get(keys);
}
);
}, },
set(items) { async set(items) {
return measureOp( const db = await getDB();
ExtensionTelemetry.storageLocalSetIDB, const changes = await db.set(items, {
context.extension, serialize: ExtensionStorage.serialize,
async () => { });
const db = await getDB();
const changes = await db.set(items, {
serialize: ExtensionStorage.serialize,
});
if (changes) { if (changes) {
fireOnChanged(changes); fireOnChanged(changes);
} }
}
);
}, },
async remove(keys) { async remove(keys) {
const db = await getDB(); const db = await getDB();

View File

@ -35,27 +35,27 @@ extensions.registerModules({
}, },
extension: { extension: {
url: "chrome://extensions/content/child/ext-extension.js", url: "chrome://extensions/content/child/ext-extension.js",
scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"], scopes: ["addon_child", "content_child", "devtools_child"],
paths: [["extension"]], paths: [["extension"]],
}, },
i18n: { i18n: {
url: "chrome://extensions/content/parent/ext-i18n.js", url: "chrome://extensions/content/parent/ext-i18n.js",
scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"], scopes: ["addon_child", "content_child", "devtools_child"],
paths: [["i18n"]], paths: [["i18n"]],
}, },
runtime: { runtime: {
url: "chrome://extensions/content/child/ext-runtime.js", url: "chrome://extensions/content/child/ext-runtime.js",
scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"], scopes: ["addon_child", "content_child", "devtools_child"],
paths: [["runtime"]], paths: [["runtime"]],
}, },
storage: { storage: {
url: "chrome://extensions/content/child/ext-storage.js", url: "chrome://extensions/content/child/ext-storage.js",
scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"], scopes: ["addon_child", "content_child", "devtools_child"],
paths: [["storage"]], paths: [["storage"]],
}, },
test: { test: {
url: "chrome://extensions/content/child/ext-test.js", url: "chrome://extensions/content/child/ext-test.js",
scopes: ["addon_child", "content_child", "devtools_child", "proxy_script"], scopes: ["addon_child", "content_child", "devtools_child"],
paths: [["test"]], paths: [["test"]],
}, },
userScripts: { userScripts: {

View File

@ -0,0 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
ChromeUtils.import("resource://gre/modules/ExtensionProcessScript.jsm");

View File

@ -4,7 +4,7 @@
toolkit.jar: toolkit.jar:
% content extensions %content/extensions/ % content extensions %content/extensions/
content/extensions/dummy.xul content/extensions/dummy.xhtml
content/extensions/ext-browser-content.js content/extensions/ext-browser-content.js
content/extensions/ext-toolkit.json content/extensions/ext-toolkit.json
content/extensions/parent/ext-alarms.js (parent/ext-alarms.js) content/extensions/parent/ext-alarms.js (parent/ext-alarms.js)

View File

@ -17,12 +17,12 @@ EXTRA_JS_MODULES += [
'ExtensionPermissions.jsm', 'ExtensionPermissions.jsm',
'ExtensionPreferencesManager.jsm', 'ExtensionPreferencesManager.jsm',
'ExtensionProcessScript.jsm', 'ExtensionProcessScript.jsm',
'extensionProcessScriptLoader.js',
'ExtensionSettingsStore.jsm', 'ExtensionSettingsStore.jsm',
'ExtensionShortcuts.jsm', 'ExtensionShortcuts.jsm',
'ExtensionStorage.jsm', 'ExtensionStorage.jsm',
'ExtensionStorageIDB.jsm', 'ExtensionStorageIDB.jsm',
'ExtensionStorageSync.jsm', 'ExtensionStorageSync.jsm',
'ExtensionTelemetry.jsm',
'ExtensionUtils.jsm', 'ExtensionUtils.jsm',
'FindContent.jsm', 'FindContent.jsm',
'MatchURLFilters.jsm', 'MatchURLFilters.jsm',
@ -30,8 +30,9 @@ EXTRA_JS_MODULES += [
'MessageManagerProxy.jsm', 'MessageManagerProxy.jsm',
'NativeManifests.jsm', 'NativeManifests.jsm',
'NativeMessaging.jsm', 'NativeMessaging.jsm',
'onExtensionBrowser.js',
'PerformanceCounters.jsm', 'PerformanceCounters.jsm',
'ProxyScriptContext.jsm', 'ProxyChannelFilter.jsm',
'Schemas.jsm', 'Schemas.jsm',
'WebNavigation.jsm', 'WebNavigation.jsm',
'WebNavigationContent.js', 'WebNavigationContent.js',

View File

@ -0,0 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
Services.obs.notifyObservers(this, "tab-content-frameloader-created");

View File

@ -5,11 +5,6 @@ var { ExtensionParent } = ChromeUtils.import(
); );
var { HiddenExtensionPage, promiseExtensionViewLoaded } = ExtensionParent; var { HiddenExtensionPage, promiseExtensionViewLoaded } = ExtensionParent;
ChromeUtils.defineModuleGetter(
this,
"ExtensionTelemetry",
"resource://gre/modules/ExtensionTelemetry.jsm"
);
ChromeUtils.defineModuleGetter( ChromeUtils.defineModuleGetter(
this, this,
"PrivateBrowsingUtils", "PrivateBrowsingUtils",
@ -42,8 +37,6 @@ class BackgroundPage extends HiddenExtensionPage {
async build() { async build() {
const { extension } = this; const { extension } = this;
ExtensionTelemetry.backgroundPageLoad.stopwatchStart(extension, this);
let context; let context;
try { try {
await this.createBrowserElement(); await this.createBrowserElement();
@ -65,13 +58,11 @@ class BackgroundPage extends HiddenExtensionPage {
} catch (e) { } catch (e) {
// Extension was down before the background page has loaded. // Extension was down before the background page has loaded.
Cu.reportError(e); Cu.reportError(e);
ExtensionTelemetry.backgroundPageLoad.stopwatchCancel(extension, this); EventManager.clearPrimedListeners(this.extension, false);
extension.emit("background-page-aborted"); extension.emit("background-page-aborted");
return; return;
} }
ExtensionTelemetry.backgroundPageLoad.stopwatchFinish(extension, this);
if (context) { if (context) {
// Wait until all event listeners registered by the script so far // Wait until all event listeners registered by the script so far
// to be handled. // to be handled.

View File

@ -79,7 +79,7 @@ class ContentScriptParent {
return blobURL; return blobURL;
}; };
if (details.js && details.js.length > 0) { if (details.js && details.js.length) {
options.jsPaths = details.js.map(data => { options.jsPaths = details.js.map(data => {
if (data.file) { if (data.file) {
return data.file; return data.file;
@ -89,7 +89,7 @@ class ContentScriptParent {
}); });
} }
if (details.css && details.css.length > 0) { if (details.css && details.css.length) {
options.cssPaths = details.css.map(data => { options.cssPaths = details.css.map(data => {
if (data.file) { if (data.file) {
return data.file; return data.file;

View File

@ -19,7 +19,6 @@ var { ExtensionError } = ExtensionUtils;
const CONTAINER_PREF_INSTALL_DEFAULTS = { const CONTAINER_PREF_INSTALL_DEFAULTS = {
"privacy.userContext.enabled": true, "privacy.userContext.enabled": true,
"privacy.userContext.longPressBehavior": 2,
"privacy.userContext.ui.enabled": true, "privacy.userContext.ui.enabled": true,
"privacy.usercontext.about_newtab_segregation.enabled": true, "privacy.usercontext.about_newtab_segregation.enabled": true,
"privacy.userContext.extension": undefined, "privacy.userContext.extension": undefined,

View File

@ -556,7 +556,7 @@ this.downloads = class extends ExtensionAPI {
} }
if (filename != null) { if (filename != null) {
if (filename.length == 0) { if (!filename.length) {
return Promise.reject({ message: "filename must not be empty" }); return Promise.reject({ message: "filename must not be empty" });
} }
@ -1067,7 +1067,7 @@ this.downloads = class extends ExtensionAPI {
}; };
} }
}); });
if (Object.keys(changes).length > 0) { if (Object.keys(changes).length) {
changes.id = item.id; changes.id = item.id;
fire.async(changes); fire.async(changes);
} }

View File

@ -57,7 +57,7 @@ this.permissions = class extends ExtensionAPI {
) )
); );
if (permissions.length == 0 && origins.length == 0) { if (!permissions.length && !origins.length) {
return true; return true;
} }

View File

@ -4,15 +4,10 @@
"use strict"; "use strict";
ChromeUtils.defineModuleGetter(
this,
"ProxyScriptContext",
"resource://gre/modules/ProxyScriptContext.jsm"
);
ChromeUtils.defineModuleGetter( ChromeUtils.defineModuleGetter(
this, this,
"ProxyChannelFilter", "ProxyChannelFilter",
"resource://gre/modules/ProxyScriptContext.jsm" "resource://gre/modules/ProxyChannelFilter.jsm"
); );
var { ExtensionPreferencesManager } = ChromeUtils.import( var { ExtensionPreferencesManager } = ChromeUtils.import(
"resource://gre/modules/ExtensionPreferencesManager.jsm" "resource://gre/modules/ExtensionPreferencesManager.jsm"
@ -21,9 +16,6 @@ var { ExtensionPreferencesManager } = ChromeUtils.import(
var { ExtensionError } = ExtensionUtils; var { ExtensionError } = ExtensionUtils;
var { getSettingsAPI } = ExtensionPreferencesManager; var { getSettingsAPI } = ExtensionPreferencesManager;
// WeakMap[Extension -> ProxyScriptContext]
const proxyScriptContextMap = new WeakMap();
const proxySvc = Ci.nsIProtocolProxyService; const proxySvc = Ci.nsIProtocolProxyService;
const PROXY_TYPES_MAP = new Map([ const PROXY_TYPES_MAP = new Map([
@ -134,16 +126,6 @@ function registerProxyFilterEvent(
} }
this.proxy = class extends ExtensionAPI { this.proxy = class extends ExtensionAPI {
onShutdown() {
let { extension } = this;
let proxyScriptContext = proxyScriptContextMap.get(extension);
if (proxyScriptContext) {
proxyScriptContext.unload();
proxyScriptContextMap.delete(extension);
}
}
primeListener(extension, event, fire, params) { primeListener(extension, event, fire, params) {
if (event === "onRequest") { if (event === "onRequest") {
return registerProxyFilterEvent(undefined, extension, fire, ...params); return registerProxyFilterEvent(undefined, extension, fire, ...params);
@ -153,45 +135,8 @@ this.proxy = class extends ExtensionAPI {
getAPI(context) { getAPI(context) {
let { extension } = context; let { extension } = context;
// Leaving as non-persistent. By itself it's not useful since proxy-error
// is emitted from the proxy filter.
let onError = new EventManager({
context,
name: "proxy.onError",
register: fire => {
let listener = (name, error) => {
fire.async(error);
};
extension.on("proxy-error", listener);
return () => {
extension.off("proxy-error", listener);
};
},
}).api();
return { return {
proxy: { proxy: {
register(url) {
this.unregister();
let proxyScriptContext = new ProxyScriptContext(extension, url);
if (proxyScriptContext.load()) {
proxyScriptContextMap.set(extension, proxyScriptContext);
}
},
unregister() {
// Unload the current proxy script if one is loaded.
if (proxyScriptContextMap.has(extension)) {
proxyScriptContextMap.get(extension).unload();
proxyScriptContextMap.delete(extension);
}
},
registerProxyScript(url) {
this.register(url);
},
onRequest: new EventManager({ onRequest: new EventManager({
context, context,
name: `proxy.onRequest`, name: `proxy.onRequest`,
@ -210,10 +155,21 @@ this.proxy = class extends ExtensionAPI {
}, },
}).api(), }).api(),
onError, // Leaving as non-persistent. By itself it's not useful since proxy-error
// is emitted from the proxy filter.
// TODO Bug 1388619 deprecate onProxyError. onError: new EventManager({
onProxyError: onError, context,
name: "proxy.onError",
register: fire => {
let listener = (name, error) => {
fire.async(error);
};
extension.on("proxy-error", listener);
return () => {
extension.off("proxy-error", listener);
};
},
}).api(),
settings: Object.assign( settings: Object.assign(
getSettingsAPI( getSettingsAPI(
@ -301,7 +257,7 @@ this.proxy = class extends ExtensionAPI {
// Match what about:preferences does with proxy settings // Match what about:preferences does with proxy settings
// since the proxy service does not check the value // since the proxy service does not check the value
// of share_proxy_settings. // of share_proxy_settings.
for (let prop of ["ftp", "ssl", "socks"]) { for (let prop of ["ftp", "ssl"]) {
value[prop] = value.http; value[prop] = value.http;
} }
} }

View File

@ -841,14 +841,14 @@ class WindowBase {
} }
/** /**
* @property {nsIXULWindow} xulWindow * @property {nsIAppWindow} appWindow
* The nsIXULWindow object for this browser window. * The nsIAppWindow object for this browser window.
* @readonly * @readonly
*/ */
get xulWindow() { get appWindow() {
return this.window.docShell.treeOwner return this.window.docShell.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor) .QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIXULWindow); .getInterface(Ci.nsIAppWindow);
} }
/** /**
@ -874,7 +874,7 @@ class WindowBase {
* @readonly * @readonly
*/ */
get type() { get type() {
let { chromeFlags } = this.xulWindow; let { chromeFlags } = this.appWindow;
if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) { if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) {
return "popup"; return "popup";

View File

@ -1,9 +1,5 @@
"use strict"; "use strict";
// This file expects tabTracker to be defined in the global scope (e.g.
// by ext-utils.js).
/* global tabTracker */
ChromeUtils.defineModuleGetter( ChromeUtils.defineModuleGetter(
this, this,
"WebRequest", "WebRequest",
@ -21,27 +17,7 @@ function registerEvent(
remoteTab = null remoteTab = null
) { ) {
let listener = async data => { let listener = async data => {
let browserData = { tabId: -1, windowId: -1 }; let event = data.serialize(eventName, extension);
if (data.browser) {
browserData = tabTracker.getBrowserData(data.browser);
}
if (filter.tabId != null && browserData.tabId != filter.tabId) {
return;
}
if (filter.windowId != null && browserData.windowId != filter.windowId) {
return;
}
let event = data.serialize(eventName);
event.tabId = browserData.tabId;
if (data.originAttributes) {
event.incognito = data.originAttributes.privateBrowsingId > 0;
if (extension.hasPermission("cookies")) {
event.cookieStoreId = getCookieStoreIdForOriginAttributes(
data.originAttributes
);
}
}
if (data.registerTraceableChannel) { if (data.registerTraceableChannel) {
// If this is a primed listener, no tabParent was passed in here, // If this is a primed listener, no tabParent was passed in here,
// but the convert() callback later in this function will be called // but the convert() callback later in this function will be called
@ -74,10 +50,10 @@ function registerEvent(
if (filter.types) { if (filter.types) {
filter2.types = filter.types; filter2.types = filter.types;
} }
if (filter.tabId) { if (filter.tabId !== undefined) {
filter2.tabId = filter.tabId; filter2.tabId = filter.tabId;
} }
if (filter.windowId) { if (filter.windowId !== undefined) {
filter2.windowId = filter.windowId; filter2.windowId = filter.windowId;
} }
if (filter.incognito !== undefined) { if (filter.incognito !== undefined) {
@ -106,7 +82,7 @@ function registerEvent(
let listenerDetails = { let listenerDetails = {
addonId: extension.id, addonId: extension.id,
extension: extension.policy, policy: extension.policy,
blockingAllowed, blockingAllowed,
}; };
WebRequest[eventName].addListener(listener, filter2, info2, listenerDetails); WebRequest[eventName].addListener(listener, filter2, info2, listenerDetails);
@ -166,7 +142,7 @@ this.webRequest = class extends ExtensionAPI {
getSecurityInfo: function(requestId, options = {}) { getSecurityInfo: function(requestId, options = {}) {
return WebRequest.getSecurityInfo({ return WebRequest.getSecurityInfo({
id: requestId, id: requestId,
extension: context.extension.policy, policy: context.extension.policy,
remoteTab: context.xulBrowser.frameLoader.remoteTab, remoteTab: context.xulBrowser.frameLoader.remoteTab,
options, options,
}); });

View File

@ -178,10 +178,29 @@
}, },
"content_security_policy": { "content_security_policy": {
"type": "string",
"optional": true, "optional": true,
"format": "contentSecurityPolicy", "onError": "warn",
"onError": "warn" "choices": [
{
"type": "string",
"format": "contentSecurityPolicy"
},
{
"type": "object",
"properties": {
"extension_pages": {
"type": "string",
"optional": true,
"format": "contentSecurityPolicy"
},
"content_scripts": {
"type": "string",
"optional": true,
"format": "contentSecurityPolicy"
}
}
}
]
}, },
"permissions": { "permissions": {

View File

@ -15,7 +15,7 @@
}, },
{ {
"namespace": "proxy", "namespace": "proxy",
"description": "Use the browser.proxy API to register proxy scripts in Firefox. Proxy scripts in Firefox are proxy auto-config files with extra contextual information and support for additional return types.", "description": "Provides access to global proxy settings for Firefox and proxy event listeners to handle dynamic proxy implementations.",
"permissions": ["proxy"], "permissions": ["proxy"],
"types": [ "types": [
{ {
@ -96,44 +96,6 @@
"description": "Configures proxy settings. This setting's value is an object of type ProxyConfig." "description": "Configures proxy settings. This setting's value is an object of type ProxyConfig."
} }
}, },
"functions": [
{
"name": "register",
"type": "function",
"deprecated": "proxy.register has been deprecated and will be removed in Firefox 71.",
"description": "Registers the proxy script for the extension.",
"async": true,
"parameters": [
{
"name": "url",
"type": "string",
"format": "strictRelativeUrl"
}
]
},
{
"name": "unregister",
"type": "function",
"deprecated": "proxy.unregister has been deprecated and will be removed in Firefox 71.",
"description": "Unregisters the proxy script for the extension.",
"async": true,
"parameters": []
},
{
"name": "registerProxyScript",
"type": "function",
"deprecated": "proxy.registerProxyScript has been deprecated and will be removed in Firefox 71.",
"description": "Registers the proxy script for the extension. This is an alias for proxy.register.",
"async": true,
"parameters": [
{
"name": "url",
"type": "string",
"format": "strictRelativeUrl"
}
]
}
],
"events": [ "events": [
{ {
"name": "onRequest", "name": "onRequest",

View File

@ -19,7 +19,7 @@
}, },
{ {
"namespace": "runtime", "namespace": "runtime",
"allowedContexts": ["content", "devtools", "proxy"], "allowedContexts": ["content", "devtools"],
"description": "Use the <code>browser.runtime</code> API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs.", "description": "Use the <code>browser.runtime</code> API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs.",
"types": [ "types": [
{ {
@ -334,7 +334,7 @@
"name": "sendMessage", "name": "sendMessage",
"type": "function", "type": "function",
"allowAmbiguousOptionalArguments": true, "allowAmbiguousOptionalArguments": true,
"allowedContexts": ["content", "devtools", "proxy"], "allowedContexts": ["content", "devtools"],
"description": "Sends a single message to event listeners within your extension/app or a different extension/app. Similar to $(ref:runtime.connect) but only sends a single message, with an optional response. If sending to your extension, the $(ref:runtime.onMessage) event will be fired in each page, or $(ref:runtime.onMessageExternal), if a different extension. Note that extensions cannot send messages to content scripts using this method. To send messages to content scripts, use $(ref:tabs.sendMessage).", "description": "Sends a single message to event listeners within your extension/app or a different extension/app. Similar to $(ref:runtime.connect) but only sends a single message, with an optional response. If sending to your extension, the $(ref:runtime.onMessage) event will be fired in each page, or $(ref:runtime.onMessageExternal), if a different extension. Note that extensions cannot send messages to content scripts using this method. To send messages to content scripts, use $(ref:tabs.sendMessage).",
"async": "responseCallback", "async": "responseCallback",
"parameters": [ "parameters": [
@ -344,8 +344,7 @@
"type": "object", "type": "object",
"name": "options", "name": "options",
"properties": { "properties": {
"includeTlsChannelId": { "type": "boolean", "optional": true, "unsupported": true, "description": "Whether the TLS channel ID will be passed into onMessageExternal for processes that are listening for the connection event." }, "includeTlsChannelId": { "type": "boolean", "optional": true, "unsupported": true, "description": "Whether the TLS channel ID will be passed into onMessageExternal for processes that are listening for the connection event." }
"toProxyScript": { "type": "boolean", "optional": true, "description": "If true, the message will be directed to the extension's proxy sandbox."}
}, },
"optional": true "optional": true
}, },
@ -553,7 +552,7 @@
{ {
"name": "onMessage", "name": "onMessage",
"type": "function", "type": "function",
"allowedContexts": ["content", "devtools", "proxy"], "allowedContexts": ["content", "devtools"],
"description": "Fired when a message is sent from either an extension process or a content script.", "description": "Fired when a message is sent from either an extension process or a content script.",
"parameters": [ "parameters": [
{"name": "message", "type": "any", "optional": true, "description": "The message sent by the calling script."}, {"name": "message", "type": "any", "optional": true, "description": "The message sent by the calling script."},

View File

@ -41,7 +41,6 @@
"object", "object",
"object_subrequest", "object_subrequest",
"xmlhttprequest", "xmlhttprequest",
"xbl",
"xslt", "xslt",
"ping", "ping",
"beacon", "beacon",
@ -115,7 +114,7 @@
"type": "array", "type": "array",
"optional": true, "optional": true,
"description": "A list of request types. Requests that cannot match any of the types will be filtered out.", "description": "A list of request types. Requests that cannot match any of the types will be filtered out.",
"items": { "$ref": "ResourceType" }, "items": { "$ref": "ResourceType", "onError": "warn" },
"minItems": 1 "minItems": 1
}, },
"tabId": { "type": "integer", "optional": true }, "tabId": { "type": "integer", "optional": true },

View File

@ -41,3 +41,4 @@ skip-if = verify
[browser_ext_themes_findbar.js] [browser_ext_themes_findbar.js]
[browser_ext_themes_warnings.js] [browser_ext_themes_warnings.js]
[browser_ext_themes_highlight.js] [browser_ext_themes_highlight.js]
[browser_ext_windows_popup_title.js]

View File

@ -54,10 +54,8 @@ add_task(async function test_popup_styling(browser, accDoc) {
// Open the information arrow panel // Open the information arrow panel
await openIdentityPopup(); await openIdentityPopup();
let arrowContent = document.getAnonymousElementByAttribute( let arrowContent = gIdentityHandler._identityPopup.shadowRoot.querySelector(
gIdentityHandler._identityPopup, ".panel-arrowcontent"
"class",
"panel-arrowcontent"
); );
let arrowContentComputedStyle = window.getComputedStyle(arrowContent); let arrowContentComputedStyle = window.getComputedStyle(arrowContent);
// Ensure popup background color was set properly // Ensure popup background color was set properly

View File

@ -101,26 +101,32 @@ add_task(async function test_popup_url() {
"Should get maxResults=" + maxResults + " results" "Should get maxResults=" + maxResults + " results"
); );
let popup = UrlbarTestUtils.getPanel(window); let popup = gURLBar.view.panel;
let popupCS = window.getComputedStyle(popup);
Assert.equal( if (!gURLBar.megabar) {
popupCS.backgroundColor, // The urlbar popup supports these colors only with the legacy non-megabar
`rgb(${hexToRGB(POPUP_COLOR).join(", ")})`, // design. With megabar, the popup visually extends the textbox and use its
`Popup background color should be set to ${POPUP_COLOR}` // colors.
); let popupCS = window.getComputedStyle(popup);
Assert.equal( Assert.equal(
popupCS.borderBottomColor, popupCS.backgroundColor,
`rgb(${hexToRGB(CHROME_CONTENT_SEPARATOR_COLOR).join(", ")})`, `rgb(${hexToRGB(POPUP_COLOR).join(", ")})`,
`Popup bottom color should be set to ${CHROME_CONTENT_SEPARATOR_COLOR}` `Popup background color should be set to ${POPUP_COLOR}`
); );
Assert.equal( Assert.equal(
popupCS.color, popupCS.borderBottomColor,
`rgb(${hexToRGB(POPUP_TEXT_COLOR_DARK).join(", ")})`, `rgb(${hexToRGB(CHROME_CONTENT_SEPARATOR_COLOR).join(", ")})`,
`Popup color should be set to ${POPUP_TEXT_COLOR_DARK}` `Popup bottom color should be set to ${CHROME_CONTENT_SEPARATOR_COLOR}`
); );
Assert.equal(
popupCS.color,
`rgb(${hexToRGB(POPUP_TEXT_COLOR_DARK).join(", ")})`,
`Popup color should be set to ${POPUP_TEXT_COLOR_DARK}`
);
}
// Set the selected attribute to true to test the highlight popup properties // Set the selected attribute to true to test the highlight popup properties
UrlbarTestUtils.setSelectedIndex(window, 1); UrlbarTestUtils.setSelectedIndex(window, 1);
@ -158,8 +164,8 @@ add_task(async function test_popup_url() {
let root = document.documentElement; let root = document.documentElement;
Assert.equal( Assert.equal(
root.getAttribute("lwt-popup-brighttext"), root.hasAttribute("lwt-popup-brighttext"),
"", false,
"brighttext should not be set!" "brighttext should not be set!"
); );
Assert.equal( Assert.equal(
@ -200,12 +206,16 @@ add_task(async function test_popup_url() {
await extension.startup(); await extension.startup();
popupCS = window.getComputedStyle(popup); if (!gURLBar.megabar) {
Assert.equal( // The urlbar popup supports this color only with the legacy non-megabar
popupCS.color, // design. With megabar, the popup visually extends the textbox and use its
`rgb(${hexToRGB(POPUP_TEXT_COLOR_BRIGHT).join(", ")})`, // colors.
`Popup color should be set to ${POPUP_TEXT_COLOR_BRIGHT}` Assert.equal(
); window.getComputedStyle(popup).color,
`rgb(${hexToRGB(POPUP_TEXT_COLOR_BRIGHT).join(", ")})`,
`Popup color should be set to ${POPUP_TEXT_COLOR_BRIGHT}`
);
}
Assert.equal( Assert.equal(
window.getComputedStyle(urlResult.element.url).color, window.getComputedStyle(urlResult.element.url).color,
@ -233,8 +243,8 @@ add_task(async function test_popup_url() {
"brighttext should be set to true!" "brighttext should be set to true!"
); );
Assert.equal( Assert.equal(
root.getAttribute("lwt-popup-darktext"), root.hasAttribute("lwt-popup-darktext"),
"", false,
"darktext should not be set!" "darktext should not be set!"
); );
@ -243,26 +253,29 @@ add_task(async function test_popup_url() {
// Check to see if popup-brighttext and secondary color are not set after // Check to see if popup-brighttext and secondary color are not set after
// unload of theme // unload of theme
Assert.equal( Assert.equal(
root.getAttribute("lwt-popup-brighttext"), root.hasAttribute("lwt-popup-brighttext"),
"", false,
"brighttext should not be set!" "brighttext should not be set!"
); );
Assert.equal( Assert.equal(
root.getAttribute("lwt-popup-darktext"), root.hasAttribute("lwt-popup-darktext"),
"", false,
"darktext should not be set!" "darktext should not be set!"
); );
// Calculate what GrayText should be. May differ between platforms. // Calculate what GrayText should be. Differs between platforms.
let span = document.createXULElement("span"); // We don't use graytext for urlbar results on Mac as it's too faint.
span.style.color = "GrayText"; if (AppConstants.platform != "macosx") {
document.documentElement.appendChild(span); let span = document.createXULElement("span");
let GRAY_TEXT = window.getComputedStyle(span).color; span.style.color = "GrayText";
span.remove(); document.documentElement.appendChild(span);
let GRAY_TEXT = window.getComputedStyle(span).color;
span.remove();
Assert.equal( Assert.equal(
window.getComputedStyle(urlResult.element.separator, ":before").color, window.getComputedStyle(urlResult.element.separator, ":before").color,
GRAY_TEXT, GRAY_TEXT,
`Urlbar popup separator color should be set to ${GRAY_TEXT}` `Urlbar popup separator color should be set to ${GRAY_TEXT}`
); );
}
}); });

View File

@ -29,10 +29,9 @@ add_task(async function test_support_selection() {
}); });
await extension.startup(); await extension.startup();
let urlBar = document.querySelector("#urlbar");
let fields = [ let fields = [
document.getAnonymousElementByAttribute(urlBar, "anonid", "input"), gURLBar.inputField,
document.querySelector("#searchbar .searchbar-textbox"), document.querySelector("#searchbar .searchbar-textbox"),
].filter(field => { ].filter(field => {
let bounds = field.getBoundingClientRect(); let bounds = field.getBoundingClientRect();

View File

@ -21,11 +21,7 @@ add_task(async function test_support_tab_line() {
info("Checking selected tab line color"); info("Checking selected tab line color");
let selectedTab = document.querySelector(".tabbrowser-tab[selected]"); let selectedTab = document.querySelector(".tabbrowser-tab[selected]");
let line = document.getAnonymousElementByAttribute( let line = selectedTab.querySelector(".tab-line");
selectedTab,
"class",
"tab-line"
);
Assert.equal( Assert.equal(
window.getComputedStyle(line).backgroundColor, window.getComputedStyle(line).backgroundColor,
`rgb(${hexToRGB(TAB_LINE_COLOR).join(", ")})`, `rgb(${hexToRGB(TAB_LINE_COLOR).join(", ")})`,

View File

@ -34,11 +34,7 @@ add_task(async function test_support_tab_loading_filling() {
selectedTab.setAttribute("busy", "true"); selectedTab.setAttribute("busy", "true");
selectedTab.setAttribute("progress", "true"); selectedTab.setAttribute("progress", "true");
let throbber = document.getAnonymousElementByAttribute( let throbber = selectedTab.throbber;
selectedTab,
"class",
"tab-throbber"
);
Assert.equal( Assert.equal(
window.getComputedStyle(throbber, "::before").fill, window.getComputedStyle(throbber, "::before").fill,
`rgb(${hexToRGB(TAB_LOADING_COLOR).join(", ")})`, `rgb(${hexToRGB(TAB_LOADING_COLOR).join(", ")})`,

View File

@ -24,21 +24,13 @@ add_task(async function test_tab_background_color_property() {
let openTab = document.querySelector( let openTab = document.querySelector(
".tabbrowser-tab[visuallyselected=true]" ".tabbrowser-tab[visuallyselected=true]"
); );
let openTabBackground = document.getAnonymousElementByAttribute( let openTabBackground = openTab.querySelector(".tab-background");
openTab,
"class",
"tab-background"
);
let selectedTab = await BrowserTestUtils.openNewForegroundTab( let selectedTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser, gBrowser,
"about:blank" "about:blank"
); );
let selectedTabBackground = document.getAnonymousElementByAttribute( let selectedTabBackground = selectedTab.querySelector(".tab-background");
selectedTab,
"class",
"tab-background"
);
let openTabGradient = window let openTabGradient = window
.getComputedStyle(openTabBackground) .getComputedStyle(openTabBackground)

View File

@ -31,11 +31,7 @@ add_task(async function test_button_background_properties() {
await extension.startup(); await extension.startup();
let toolbarButton = document.querySelector("#home-button"); let toolbarButton = document.querySelector("#home-button");
let toolbarButtonIcon = document.getAnonymousElementByAttribute( let toolbarButtonIcon = toolbarButton.icon;
toolbarButton,
"class",
"toolbarbutton-icon"
);
let toolbarButtonIconCS = window.getComputedStyle(toolbarButtonIcon); let toolbarButtonIconCS = window.getComputedStyle(toolbarButtonIcon);
InspectorUtils.addPseudoClassLock(toolbarButton, ":hover"); InspectorUtils.addPseudoClassLock(toolbarButton, ":hover");

View File

@ -0,0 +1,61 @@
"use strict";
// Check that extension popup windows contain the name of the extension
// as well as the title of the loaded document, but not the URL.
add_task(async function test_popup_title() {
const name = "custom_title_number_9_please";
const docTitle = "popup-test-title";
const extension = ExtensionTestUtils.loadExtension({
manifest: {
name,
permissions: ["tabs"],
},
async background() {
let popup;
// Called after the popup loads
browser.runtime.onMessage.addListener(async ({ docTitle }) => {
const { id } = await popup;
const { title } = await browser.windows.get(id);
browser.windows.remove(id);
browser.test.assertTrue(
title.includes(name),
"popup title must include extension name"
);
browser.test.assertTrue(
title.includes(docTitle),
"popup title must include extension document title"
);
browser.test.assertFalse(
title.includes("moz-extension:"),
"popup title must not include extension URL"
);
browser.test.notifyPass("popup-window-title");
});
popup = browser.windows.create({
url: "/index.html",
type: "popup",
});
},
files: {
"index.html": `<!doctype html>
<meta charset="utf-8">
<title>${docTitle}</title>,
<script src="index.js"></script>
`,
"index.js": `addEventListener(
"load",
() => browser.runtime.sendMessage({docTitle: document.title})
);`,
},
});
await extension.startup();
await extension.awaitFinish("popup-window-title");
await extension.unload();
});

View File

@ -19,6 +19,5 @@ sandbox.setAttribute("src", "file_simple_sandboxed_frame.html");
document.documentElement.appendChild(sandbox); document.documentElement.appendChild(sandbox);
</script> </script>
<img src="file_image_redirect.png"/> <img src="file_image_redirect.png"/>
<iframe src="data:text/plain,webRequestTest"/>
</body> </body>
</html> </html>

View File

@ -69,18 +69,7 @@ function background(events) {
// Retrieve the per-file/test expected values. // Retrieve the per-file/test expected values.
function getExpected(details) { function getExpected(details) {
let url = new URL(details.url); let url = new URL(details.url);
let filename; let filename = url.pathname.split("/").pop();
if (url.protocol == "data:") {
// See bug 1471387
if (details.originUrl == "about:newtab") {
return;
}
// pathname is everything after protocol.
filename = url.pathname;
} else {
filename = url.pathname.split("/").pop();
}
if (ignore && ignore.includes(filename)) { if (ignore && ignore.includes(filename)) {
return; return;
} }

View File

@ -17,7 +17,7 @@ add_task(async function webnav_unresolved_uri_on_expected_URI_scheme() {
let checkURLs; let checkURLs;
browser.webNavigation.onCompleted.addListener(async msg => { browser.webNavigation.onCompleted.addListener(async msg => {
if (checkURLs.length > 0) { if (checkURLs.length) {
let expectedURL = checkURLs.shift(); let expectedURL = checkURLs.shift();
browser.test.assertEq(expectedURL, msg.url, "Got the expected URL"); browser.test.assertEq(expectedURL, msg.url, "Got the expected URL");
await browser.tabs.remove(msg.tabId); await browser.tabs.remove(msg.tabId);

View File

@ -331,7 +331,7 @@ add_task(async function test_contentscript_clipboard_nocontents_read() {
// and read from it, the data transfer object contains an item of type // and read from it, the data transfer object contains an item of type
// text/plain and kind string, but we can't call getAsString on it to verify // text/plain and kind string, but we can't call getAsString on it to verify
// that at least it is an empty string because the callback never gets invoked. // that at least it is an empty string because the callback never gets invoked.
if (dataT.items.length == 0 || if (!dataT.items.length ||
(dataT.items.length == 1 && dataT.items[0].type == "text/plain" && (dataT.items.length == 1 && dataT.items[0].type == "text/plain" &&
dataT.items[0].kind == "string")) { dataT.items[0].kind == "string")) {
browser.test.succeed("Read promise successfully resolved"); browser.test.succeed("Read promise successfully resolved");

View File

@ -139,7 +139,7 @@ add_task(async function test_protocolHandler() {
let window = await BrowserTestUtils.domWindowOpened(undefined, win => { let window = await BrowserTestUtils.domWindowOpened(undefined, win => {
return BrowserTestUtils.waitForEvent(win, "load", false, event => { return BrowserTestUtils.waitForEvent(win, "load", false, event => {
let win = event.target.defaultView; let win = event.target.defaultView;
return win.document.documentElement.getAttribute("id") === "handling"; return !!win.document.querySelector("dialog#handling");
}); });
}); });
let entry = window.document.getElementById("items").firstChild; let entry = window.document.getElementById("items").firstChild;

View File

@ -355,15 +355,6 @@ add_task(async function test_webRequest_tabId_browser() {
add_task(async function test_webRequest_frames() { add_task(async function test_webRequest_frames() {
let expect = { let expect = {
"text/plain,webRequestTest": {
type: "sub_frame",
events: ["onBeforeRequest", "onCompleted"],
},
"text/plain,webRequestTest_bad": {
type: "sub_frame",
events: ["onBeforeRequest", "onCompleted"],
cancel: "onBeforeRequest",
},
"redirection.sjs": { "redirection.sjs": {
status: 302, status: 302,
type: "sub_frame", type: "sub_frame",
@ -386,9 +377,6 @@ add_task(async function test_webRequest_frames() {
}; };
extension.sendMessage("set-expected", {expect, origin: location.href}); extension.sendMessage("set-expected", {expect, origin: location.href});
await extension.awaitMessage("continue"); await extension.awaitMessage("continue");
addFrame("data:text/plain,webRequestTest");
addFrame("data:text/plain,webRequestTest_bad");
await extension.awaitMessage("cancelled");
addFrame("redirection.sjs"); addFrame("redirection.sjs");
addFrame("https://nonresolvablehostname.invalid/badrobot"); addFrame("https://nonresolvablehostname.invalid/badrobot");
await extension.awaitMessage("done"); await extension.awaitMessage("done");

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