merge mozilla-central to mozilla-beta. a=merge l10n=merge
merge mozilla-central to mozilla-beta. a=merge l10n=merge
--- a/accessible/android/AccessibleWrap.cpp
+++ b/accessible/android/AccessibleWrap.cpp
@@ -1,16 +1,17 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "AccessibleWrap.h"
#include "Accessible-inl.h"
+#include "AndroidInputType.h"
#include "DocAccessibleWrap.h"
#include "IDSet.h"
#include "JavaBuiltins.h"
#include "SessionAccessibility.h"
#include "nsAccessibilityService.h"
#include "nsIPersistentProperties2.h"
#include "nsIStringBundle.h"
#include "nsAccUtils.h"
@@ -281,20 +282,21 @@ AccessibleWrap::CreateBundle(int32_t aPa
if (!IsNaN(aMaxVal)) {
GECKOBUNDLE_PUT(rangeInfo, "max", java::sdk::Double::New(aMaxVal));
}
GECKOBUNDLE_FINISH(rangeInfo);
GECKOBUNDLE_PUT(nodeInfo, "rangeInfo", rangeInfo);
}
- nsString inputType;
- nsAccUtils::GetAccAttr(aAttributes, nsGkAtoms::textInputType, inputType);
- if (!inputType.IsEmpty()) {
- GECKOBUNDLE_PUT(nodeInfo, "inputType", jni::StringParam(inputType));
+ nsString inputTypeAttr;
+ nsAccUtils::GetAccAttr(aAttributes, nsGkAtoms::textInputType, inputTypeAttr);
+ int32_t inputType = GetInputType(inputTypeAttr);
+ if (inputType) {
+ GECKOBUNDLE_PUT(nodeInfo, "inputType", java::sdk::Integer::ValueOf(inputType));
}
nsString posinset;
nsresult rv = aAttributes->GetStringProperty(NS_LITERAL_CSTRING("posinset"), posinset);
if (NS_SUCCEEDED(rv)) {
int32_t rowIndex;
if (sscanf(NS_ConvertUTF16toUTF8(posinset).get(), "%d", &rowIndex) > 0) {
GECKOBUNDLE_START(collectionItemInfo);
@@ -457,16 +459,46 @@ AccessibleWrap::GetAndroidClass(role aRo
#include "RoleMap.h"
default:
return java::SessionAccessibility::CLASSNAME_VIEW;
}
#undef ROLE
}
+int32_t
+AccessibleWrap::GetInputType(const nsString& aInputTypeAttr)
+{
+ if (aInputTypeAttr.EqualsIgnoreCase("email")) {
+ return java::sdk::InputType::TYPE_CLASS_TEXT | java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
+ }
+
+ if (aInputTypeAttr.EqualsIgnoreCase("number")) {
+ return java::sdk::InputType::TYPE_CLASS_NUMBER;
+ }
+
+ if (aInputTypeAttr.EqualsIgnoreCase("password")) {
+ return java::sdk::InputType::TYPE_CLASS_TEXT | java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_PASSWORD;
+ }
+
+ if (aInputTypeAttr.EqualsIgnoreCase("tel")) {
+ return java::sdk::InputType::TYPE_CLASS_PHONE;
+ }
+
+ if (aInputTypeAttr.EqualsIgnoreCase("text")) {
+ return java::sdk::InputType::TYPE_CLASS_TEXT | java::sdk::InputType::TYPE_TEXT_VARIATION_WEB_EDIT_TEXT;
+ }
+
+ if (aInputTypeAttr.EqualsIgnoreCase("url")) {
+ return java::sdk::InputType::TYPE_CLASS_TEXT | java::sdk::InputType::TYPE_TEXT_VARIATION_URI;
+ }
+
+ return 0;
+}
+
void
AccessibleWrap::DOMNodeID(nsString& aDOMNodeID)
{
if (mContent) {
nsAtom* id = mContent->GetID();
if (id) {
id->ToString(aDOMNodeID);
}
--- a/accessible/android/AccessibleWrap.h
+++ b/accessible/android/AccessibleWrap.h
@@ -52,16 +52,18 @@ protected:
const nsTArray<int32_t>& aChildren) const;
// IDs should be a positive 32bit integer.
static int32_t AcquireID();
static void ReleaseID(int32_t aID);
static int32_t GetAndroidClass(role aRole);
+ static int32_t GetInputType(const nsString& aInputTypeAttr);
+
int32_t mID;
private:
void DOMNodeID(nsString& aDOMNodeID);
static void GetRoleDescription(role aRole,
nsAString& aGeckoRole,
nsAString& aRoleDescription);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1514,25 +1514,21 @@ pref("network.cookie.cookieBehavior", 4
pref("browser.contentblocking.allowlist.storage.enabled", true);
#ifdef NIGHTLY_BUILD
pref("browser.contentblocking.global-toggle.enabled", true);
#else
pref("browser.contentblocking.global-toggle.enabled", false);
#endif
-// Define a set of default features for the Content Blocking UI
-#ifdef EARLY_BETA_OR_EARLIER
-pref("browser.contentblocking.fastblock.ui.enabled", true);
-pref("browser.contentblocking.fastblock.control-center.ui.enabled", true);
-#else
+// Disable the UI for FastBlock in product.
pref("browser.contentblocking.fastblock.ui.enabled", false);
pref("browser.contentblocking.fastblock.control-center.ui.enabled", false);
-#endif
+// Define a set of default features for the Content Blocking UI.
pref("browser.contentblocking.trackingprotection.ui.enabled", true);
pref("browser.contentblocking.trackingprotection.control-center.ui.enabled", true);
pref("browser.contentblocking.rejecttrackers.ui.enabled", true);
pref("browser.contentblocking.rejecttrackers.ui.recommended", true);
pref("browser.contentblocking.rejecttrackers.control-center.ui.enabled", true);
pref("browser.contentblocking.cookies-site-data.ui.reject-trackers.recommended", true);
pref("browser.contentblocking.cookies-site-data.ui.reject-trackers.enabled", true);
@@ -1774,13 +1770,8 @@ pref("browser.fission.simulate", false);
pref("prio.publicKeyA", "35AC1C7576C7C6EDD7FED6BCFC337B34D48CB4EE45C86BEEFB40BD8875707733");
pref("prio.publicKeyB", "26E6674E65425B823F1F1D5F96E3BB3EF9E406EC7FBA7DEF8B08A35DD135AF50");
#endif
// Whether or not Prio-encoded Telemetry will be sent along with the main ping.
#if defined(NIGHTLY_BUILD) && defined(MOZ_LIBPRIO)
pref("prio.enabled", true);
#endif
-
-#ifdef NIGHTLY_BUILD
-pref("browser.fastblock.enabled", true);
-#endif
-
--- a/browser/base/content/browser-contentblocking.js
+++ b/browser/base/content/browser-contentblocking.js
@@ -121,17 +121,16 @@ var TrackingProtection = {
},
isBlockerActivated(state) {
return state & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT;
},
};
var ThirdPartyCookies = {
- reportBreakageLabel: "cookierestrictions",
telemetryIdentifier: "cr",
PREF_ENABLED: "network.cookie.cookieBehavior",
PREF_REPORT_BREAKAGE_ENABLED: "browser.contentblocking.rejecttrackers.reportBreakage.enabled",
PREF_ENABLED_VALUES: [
// These values match the ones exposed under the Content Blocking section
// of the Preferences UI.
Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN, // Block all third-party cookies
Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER, // Block third-party cookies from trackers
@@ -139,22 +138,59 @@ var ThirdPartyCookies = {
PREF_UI_ENABLED: "browser.contentblocking.rejecttrackers.control-center.ui.enabled",
get categoryItem() {
delete this.categoryItem;
return this.categoryItem =
document.getElementById("identity-popup-content-blocking-category-3rdpartycookies");
},
+ get reportBreakageLabel() {
+ switch (this.behaviorPref) {
+ case Ci.nsICookieService.BEHAVIOR_ACCEPT:
+ return "nocookiesblocked";
+ case Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN:
+ return "allthirdpartycookiesblocked";
+ case Ci.nsICookieService.BEHAVIOR_REJECT:
+ return "allcookiesblocked";
+ case Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN:
+ return "cookiesfromunvisitedsitesblocked";
+ default:
+ Cu.reportError(`Error: Unknown cookieBehavior pref observed: ${this.behaviorPref}`);
+ // fall through
+ case Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER:
+ return "cookierestrictions";
+ }
+ },
+
+ get categoryLabelDefault() {
+ delete this.categoryLabelDefault;
+ return this.categoryLabelDefault =
+ document.getElementById("identity-popup-content-blocking-category-label-default");
+ },
+
+ get categoryLabelTrackers() {
+ delete this.categoryLabelTrackers;
+ return this.categoryLabelTrackers =
+ document.getElementById("identity-popup-content-blocking-category-label-trackers");
+ },
+
+ updateCategoryLabel() {
+ let rejectTrackers = this.behaviorPref == Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER;
+ this.categoryLabelDefault.hidden = rejectTrackers;
+ this.categoryLabelTrackers.hidden = !rejectTrackers;
+ },
+
init() {
XPCOMUtils.defineLazyPreferenceGetter(this, "behaviorPref", this.PREF_ENABLED,
- Ci.nsICookieService.BEHAVIOR_ACCEPT);
+ Ci.nsICookieService.BEHAVIOR_ACCEPT, this.updateCategoryLabel.bind(this));
XPCOMUtils.defineLazyPreferenceGetter(this, "visible", this.PREF_UI_ENABLED, false);
XPCOMUtils.defineLazyPreferenceGetter(this, "reportBreakageEnabled",
this.PREF_REPORT_BREAKAGE_ENABLED, false);
+ this.updateCategoryLabel();
},
get enabled() {
return this.PREF_ENABLED_VALUES.includes(this.behaviorPref);
},
isBlockerActivated(state) {
return (state & Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER) != 0 ||
(state & Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN) != 0;
--- a/browser/base/content/browser-customization.js
+++ b/browser/base/content/browser-customization.js
@@ -61,8 +61,120 @@ var CustomizationHandler = {
for (let childNode of menubar.children)
childNode.setAttribute("disabled", false);
let cmd = document.getElementById("cmd_CustomizeToolbars");
cmd.removeAttribute("disabled");
gBrowser.selectedBrowser.focus();
},
};
+
+var AutoHideMenubar = {
+ get _node() {
+ delete this._node;
+ return this._node = document.getElementById("toolbar-menubar");
+ },
+
+ _contextMenuListener: {
+ contextMenu: null,
+
+ get active() {
+ return !!this.contextMenu;
+ },
+
+ init(event) {
+ // Ignore mousedowns in <menupopup>s.
+ if (event.target.closest("menupopup")) {
+ return;
+ }
+
+ let contextMenuId = AutoHideMenubar._node.getAttribute("context");
+ this.contextMenu = document.getElementById(contextMenuId);
+ this.contextMenu.addEventListener("popupshown", this);
+ this.contextMenu.addEventListener("popuphiding", this);
+ AutoHideMenubar._node.addEventListener("mousemove", this);
+ },
+ handleEvent(event) {
+ switch (event.type) {
+ case "popupshown":
+ AutoHideMenubar._node.removeEventListener("mousemove", this);
+ break;
+ case "popuphiding":
+ case "mousemove":
+ AutoHideMenubar._setInactiveAsync();
+ AutoHideMenubar._node.removeEventListener("mousemove", this);
+ this.contextMenu.removeEventListener("popuphiding", this);
+ this.contextMenu.removeEventListener("popupshown", this);
+ this.contextMenu = null;
+ break;
+ }
+ },
+ },
+
+ init() {
+ this._node.addEventListener("toolbarvisibilitychange", this);
+ if (this._node.getAttribute("autohide") == "true") {
+ this._enable();
+ }
+ },
+
+ _updateState() {
+ if (this._node.getAttribute("autohide") == "true") {
+ this._enable();
+ } else {
+ this._disable();
+ }
+ },
+
+ _events: ["DOMMenuBarInactive", "DOMMenuBarActive", "popupshowing", "mousedown"],
+ _enable() {
+ this._node.setAttribute("inactive", "true");
+ for (let event of this._events) {
+ this._node.addEventListener(event, this);
+ }
+ },
+
+ _disable() {
+ this._setActive();
+ for (let event of this._events) {
+ this._node.removeEventListener(event, this);
+ }
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "toolbarvisibilitychange":
+ this._updateState();
+ break;
+ case "popupshowing":
+ // fall through
+ case "DOMMenuBarActive":
+ this._setActive();
+ break;
+ case "mousedown":
+ if (event.button == 2) {
+ this._contextMenuListener.init(event);
+ }
+ break;
+ case "DOMMenuBarInactive":
+ if (!this._contextMenuListener.active)
+ this._setInactiveAsync();
+ break;
+ }
+ },
+
+ _setInactiveAsync() {
+ this._inactiveTimeout = setTimeout(() => {
+ if (this._node.getAttribute("autohide") == "true") {
+ this._inactiveTimeout = null;
+ this._node.setAttribute("inactive", "true");
+ }
+ }, 0);
+ },
+
+ _setActive() {
+ if (this._inactiveTimeout) {
+ clearTimeout(this._inactiveTimeout);
+ this._inactiveTimeout = null;
+ }
+ this._node.removeAttribute("inactive");
+ },
+};
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -71,20 +71,16 @@ toolbar[customizable="true"] {
}
%ifdef XP_MACOSX
#toolbar-menubar {
-moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar-menubar-stub");
}
%endif
-#toolbar-menubar[autohide="true"] {
- -moz-binding: url("chrome://browser/content/customizableui/toolbar.xml#toolbar-menubar-autohide");
-}
-
panelmultiview {
-moz-box-align: start;
}
panelmultiview[transitioning] {
pointer-events: none;
}
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -102,17 +102,17 @@ XPCOMUtils.defineLazyScriptGetter(this,
XPCOMUtils.defineLazyScriptGetter(this, "gTabsPanel",
"chrome://browser/content/browser-allTabsMenu.js");
XPCOMUtils.defineLazyScriptGetter(this, ["LightWeightThemeWebInstaller",
"gExtensionsNotifications",
"gXPInstallObserver"],
"chrome://browser/content/browser-addons.js");
XPCOMUtils.defineLazyScriptGetter(this, "ctrlTab",
"chrome://browser/content/browser-ctrlTab.js");
-XPCOMUtils.defineLazyScriptGetter(this, "CustomizationHandler",
+XPCOMUtils.defineLazyScriptGetter(this, ["CustomizationHandler", "AutoHideMenubar"],
"chrome://browser/content/browser-customization.js");
XPCOMUtils.defineLazyScriptGetter(this, ["PointerLock", "FullScreen"],
"chrome://browser/content/browser-fullScreenAndPointerLock.js");
XPCOMUtils.defineLazyScriptGetter(this, ["gGestureSupport", "gHistorySwipeAnimation"],
"chrome://browser/content/browser-gestureSupport.js");
XPCOMUtils.defineLazyScriptGetter(this, "gSafeBrowsing",
"chrome://browser/content/browser-safebrowsing.js");
XPCOMUtils.defineLazyScriptGetter(this, "gSync",
@@ -1237,16 +1237,19 @@ var gBrowserInit = {
document.documentElement.setAttribute("width", width);
document.documentElement.setAttribute("height", height);
if (width < TARGET_WIDTH && height < TARGET_HEIGHT) {
document.documentElement.setAttribute("sizemode", "maximized");
}
}
+ // Run menubar initialization first, to avoid TabsInTitlebar code picking
+ // up mutations from it and causing a reflow.
+ AutoHideMenubar.init();
// Update the chromemargin attribute so the window can be sized correctly.
window.TabBarVisibility.update();
TabsInTitlebar.init();
new LightweightThemeConsumer(document);
CompactTheme.init();
if (AppConstants.platform == "win") {
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -58,17 +58,17 @@
<textbox id="password-notification-password" type="password" show-content=""/>
<checkbox id="password-notification-visibilityToggle" hidden="true"/>
</popupnotificationcontent>
</popupnotification>
<popupnotification id="addon-progress-notification" hidden="true">
<popupnotificationcontent orient="vertical">
- <progressmeter id="addon-progress-notification-progressmeter"/>
+ <html:progress id="addon-progress-notification-progressmeter" max="100"/>
<label id="addon-progress-notification-progresstext" crop="end"/>
</popupnotificationcontent>
</popupnotification>
<popupnotification id="addon-install-confirmation-notification" hidden="true">
<popupnotificationcontent id="addon-install-confirmation-content" orient="vertical"/>
</popupnotification>
--- a/browser/base/content/test/trackingUI/browser_trackingUI_state.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_state.js
@@ -172,16 +172,28 @@ function testTrackingPage(window) {
"#identity-popup-content-blocking-category-3rdpartycookies" :
"#identity-popup-content-blocking-category-tracking-protection";
}
is(hidden(category + " > .identity-popup-content-blocking-category-add-blocking"), blockedByTP,
"Category item is" + (blockedByTP ? " not" : "") + " showing add blocking");
is(hidden(category + " > .identity-popup-content-blocking-category-state-label"), !blockedByTP,
"Category item is" + (blockedByTP ? "" : " not") + " set to blocked");
}
+
+ if (Services.prefs.getIntPref(TPC_PREF) == Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER) {
+ ok(hidden("#identity-popup-content-blocking-category-label-default"),
+ "Not showing default cookie restrictions label.");
+ ok(!hidden("#identity-popup-content-blocking-category-label-trackers"),
+ "Showing trackers cookie restrictions label.");
+ } else {
+ ok(hidden("#identity-popup-content-blocking-category-label-trackers"),
+ "Not showing trackers cookie restrictions label.");
+ ok(!hidden("#identity-popup-content-blocking-category-label-default"),
+ "Showing default cookie restrictions label.");
+ }
}
function testTrackingPageUnblocked(blockedByTP, window) {
info("Tracking content must be white-listed and not blocked");
ok(ContentBlocking.content.hasAttribute("detected"), "trackers are detected");
ok(ContentBlocking.content.hasAttribute("hasException"), "content shows exception");
let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
--- a/browser/base/content/test/trackingUI/browser_trackingUI_telemetry.js
+++ b/browser/base/content/test/trackingUI/browser_trackingUI_telemetry.js
@@ -34,19 +34,20 @@ add_task(async function setup() {
let enabledCounts =
Services.telemetry.getHistogramById("TRACKING_PROTECTION_ENABLED").snapshot().counts;
is(enabledCounts[0], 1, "TP was not enabled on start up");
let scalars = Services.telemetry.snapshotScalars(
Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT, false).parent;
- is(scalars["contentblocking.enabled"], true, "CB was enabled at startup");
- is(scalars["contentblocking.fastblock_enabled"], AppConstants.NIGHTLY_BUILD,
- "FB is enabled in Nightly");
+ is(scalars["contentblocking.enabled"], Services.prefs.getBoolPref("browser.contentblocking.enabled"),
+ "CB enabled status was recorded at startup");
+ is(scalars["contentblocking.fastblock_enabled"], Services.prefs.getBoolPref("browser.fastblock.enabled"),
+ "FB enabled status was recorded at startup");
is(scalars["contentblocking.exceptions"], 0, "no CB exceptions at startup");
});
add_task(async function testShieldHistogram() {
Services.prefs.setBoolPref(PREF, true);
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -2756,19 +2756,18 @@ file, You can obtain one at http://mozil
]]></body>
</method>
<method name="setProgress">
<parameter name="aProgress"/>
<parameter name="aMaxProgress"/>
<body><![CDATA[
if (aMaxProgress == -1) {
- this.progressmeter.setAttribute("mode", "undetermined");
+ this.progressmeter.removeAttribute("value");
} else {
- this.progressmeter.setAttribute("mode", "determined");
this.progressmeter.setAttribute("value", (aProgress * 100) / aMaxProgress);
}
let now = Date.now();
if (!this.notification.lastUpdate) {
this.notification.lastUpdate = now;
this.notification.lastProgress = aProgress;
@@ -2828,17 +2827,17 @@ file, You can obtain one at http://mozil
if (maxProgress >= 0)
maxProgress += aInstall.maxProgress;
if (aInstall.state < AddonManager.STATE_DOWNLOADED)
downloadingCount++;
});
if (downloadingCount == 0) {
this.destroy();
- this.progressmeter.setAttribute("mode", "undetermined");
+ this.progressmeter.removeAttribute("value");
let status = gNavigatorBundle.getString("addonDownloadVerifying");
this.progresstext.setAttribute("value", status);
this.progresstext.setAttribute("tooltiptext", status);
} else {
this.setProgress(progress, maxProgress);
}
]]></body>
</method>
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -100,17 +100,20 @@
<label flex="1" class="identity-popup-content-blocking-category-state-label">&contentBlocking.trackingProtection.blocking.label;</label>
<label flex="1" class="identity-popup-content-blocking-category-add-blocking text-link"
id="identity-popup-tracking-protection-add-blocking"
onclick="ContentBlocking.openPreferences('identityPopup-CB-tracking-protection'); gIdentityHandler.recordClick('tp_add_blocking');">&contentBlocking.trackingProtection.add.label;</label>
</hbox>
<hbox id="identity-popup-content-blocking-category-3rdpartycookies"
class="identity-popup-content-blocking-category" align="center" role="group">
<image class="identity-popup-content-blocking-category-icon thirdpartycookies-icon"/>
- <label flex="1" class="identity-popup-content-blocking-category-label">&contentBlocking.3rdPartyCookies.label;</label>
+ <label flex="1" id="identity-popup-content-blocking-category-label-default"
+ class="identity-popup-content-blocking-category-label">&contentBlocking.3rdPartyCookies.label;</label>
+ <label flex="1" id="identity-popup-content-blocking-category-label-trackers"
+ hidden="true" class="identity-popup-content-blocking-category-label">&contentBlocking.3rdPartyCookies.trackers.label;</label>
<label flex="1" class="identity-popup-content-blocking-category-state-label">&contentBlocking.3rdPartyCookies.blocking.label;</label>
<label flex="1" class="identity-popup-content-blocking-category-add-blocking text-link"
id="identity-popup-3rdpartycookies-add-blocking"
onclick="ContentBlocking.openPreferences('identityPopup-CB-3rdpartycookies'); gIdentityHandler.recordClick('cookies_add_blocking');">&contentBlocking.3rdPartyCookies.add.label;</label>
</hbox>
</vbox>
<button id="tracking-action-unblock"
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -628,16 +628,20 @@
</vbox>
<button id="PanelUI-panic-view-button"
label="&panicButton.view.forgetButton;"/>
</vbox>
</panelview>
<panelview id="appMenu-moreView" title="&moreMenu.label;" class="PanelUI-subView">
<vbox class="panel-subview-body">
+ <toolbarbutton id="appMenu-taskmanager-button"
+ class="subviewbutton subviewbutton-iconic"
+ label="&taskManagerCmd.label;"
+ oncommand="switchToTabHavingURI('about:performance', true)"/>
<toolbarbutton id="appMenu-characterencoding-button"
class="subviewbutton subviewbutton-nav"
label="&charsetMenu2.label;"
closemenu="none"
oncommand="PanelUI.showSubView('PanelUI-characterEncodingView', this)"/>
<toolbarbutton id="appMenu-workoffline-button"
class="subviewbutton"
label="&goOfflineCmd.label;"
--- a/browser/components/customizableui/content/toolbar.xml
+++ b/browser/components/customizableui/content/toolbar.xml
@@ -156,118 +156,20 @@
<method name="insertItem">
<body><![CDATA[
return null;
]]></body>
</method>
</implementation>
</binding>
- <!-- The toolbar-menubar-autohide and toolbar-drag bindings are almost
- verbatim copies of their toolkit counterparts - they just inherit from
- the customizableui's toolbar binding instead of toolkit's. We're currently
- OK with the maintainance burden of having two copies of a binding, since
- the long term goal is to move the customization framework into toolkit. -->
-
- <binding id="toolbar-menubar-autohide"
- extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
- <implementation>
- <constructor>
- this._setInactive();
- </constructor>
- <destructor>
- this._setActive();
- </destructor>
-
- <field name="_inactiveTimeout">null</field>
-
- <field name="_contextMenuListener"><![CDATA[({
- toolbar: this,
- contextMenu: null,
-
- get active() {
- return !!this.contextMenu;
- },
-
- init(event) {
- let node = event.target;
- while (node != this.toolbar) {
- if (node.localName == "menupopup")
- return;
- node = node.parentNode;
- }
-
- let contextMenuId = this.toolbar.getAttribute("context");
- if (!contextMenuId)
- return;
-
- this.contextMenu = document.getElementById(contextMenuId);
- if (!this.contextMenu)
- return;
-
- this.contextMenu.addEventListener("popupshown", this);
- this.contextMenu.addEventListener("popuphiding", this);
- this.toolbar.addEventListener("mousemove", this);
- },
- handleEvent(event) {
- switch (event.type) {
- case "popupshown":
- this.toolbar.removeEventListener("mousemove", this);
- break;
- case "popuphiding":
- case "mousemove":
- this.toolbar._setInactiveAsync();
- this.toolbar.removeEventListener("mousemove", this);
- this.contextMenu.removeEventListener("popuphiding", this);
- this.contextMenu.removeEventListener("popupshown", this);
- this.contextMenu = null;
- break;
- }
- },
- })]]></field>
-
- <method name="_setInactive">
- <body><![CDATA[
- this.setAttribute("inactive", "true");
- ]]></body>
- </method>
-
- <method name="_setInactiveAsync">
- <body><![CDATA[
- this._inactiveTimeout = setTimeout(function(self) {
- if (self.getAttribute("autohide") == "true") {
- self._inactiveTimeout = null;
- self._setInactive();
- }
- }, 0, this);
- ]]></body>
- </method>
-
- <method name="_setActive">
- <body><![CDATA[
- if (this._inactiveTimeout) {
- clearTimeout(this._inactiveTimeout);
- this._inactiveTimeout = null;
- }
- this.removeAttribute("inactive");
- ]]></body>
- </method>
- </implementation>
-
- <handlers>
- <handler event="DOMMenuBarActive" action="this._setActive();"/>
- <handler event="popupshowing" action="this._setActive();"/>
- <handler event="mousedown" button="2" action="this._contextMenuListener.init(event);"/>
- <handler event="DOMMenuBarInactive"><![CDATA[
- if (!this._contextMenuListener.active)
- this._setInactiveAsync();
- ]]></handler>
- </handlers>
- </binding>
-
+ <!-- The toolbar-drag binding is almost a verbatim copy of its toolkit counterpart,
+ but it inherits from the customizableui's toolbar binding instead of toolkit's.
+ This functionality will move into CustomizableUI proper as part of our move
+ away from XBL. -->
<binding id="toolbar-drag"
extends="chrome://browser/content/customizableui/toolbar.xml#toolbar">
<implementation>
<field name="_dragBindingAlive">true</field>
<constructor><![CDATA[
if (!this._draggableStarted) {
this._draggableStarted = true;
try {
--- a/browser/components/downloads/DownloadsViewUI.jsm
+++ b/browser/components/downloads/DownloadsViewUI.jsm
@@ -9,27 +9,28 @@
"use strict";
var EXPORTED_SYMBOLS = [
"DownloadsViewUI",
];
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
Downloads: "resource://gre/modules/Downloads.jsm",
DownloadUtils: "resource://gre/modules/DownloadUtils.jsm",
DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
FileUtils: "resource://gre/modules/FileUtils.jsm",
OS: "resource://gre/modules/osfile.jsm",
});
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
var gDownloadElementButtons = {
cancel: {
commandName: "downloadsCmd_cancel",
l10nId: "download-cancel",
descriptionL10nId: "download-cancel-description",
iconClass: "downloadIconCancel",
},
retry: {
@@ -172,17 +173,16 @@ this.DownloadsViewUI.DownloadElementShel
downloadListItemFragment = MozXULElement.parseXULToFragment(`
<hbox class="downloadMainArea" flex="1" align="center">
<stack>
<image class="downloadTypeIcon" validate="always"/>
<image class="downloadBlockedBadge" />
</stack>
<vbox class="downloadContainer" flex="1" pack="center">
<description class="downloadTarget" crop="center"/>
- <progressmeter class="downloadProgress" min="0" max="100"/>
<description class="downloadDetails downloadDetailsNormal"
crop="end"/>
<description class="downloadDetails downloadDetailsHover"
crop="end"/>
<description class="downloadDetails downloadDetailsButtonHover"
crop="end"/>
</vbox>
</hbox>
@@ -196,24 +196,30 @@ this.DownloadsViewUI.DownloadElementShel
this.element.setAttribute("orient", "horizontal");
this.element.setAttribute("onclick",
"DownloadsView.onDownloadClick(event);");
this.element.appendChild(document.importNode(downloadListItemFragment,
true));
for (let [propertyName, selector] of [
["_downloadTypeIcon", ".downloadTypeIcon"],
["_downloadTarget", ".downloadTarget"],
- ["_downloadProgress", ".downloadProgress"],
["_downloadDetailsNormal", ".downloadDetailsNormal"],
["_downloadDetailsHover", ".downloadDetailsHover"],
["_downloadDetailsButtonHover", ".downloadDetailsButtonHover"],
["_downloadButton", ".downloadButton"],
]) {
this[propertyName] = this.element.querySelector(selector);
}
+
+ // HTML elements can be created directly without using parseXULToFragment.
+ let progress = this._downloadProgress =
+ document.createElementNS(HTML_NS, "progress");
+ progress.className = "downloadProgress";
+ progress.setAttribute("max", "100");
+ this._downloadTarget.insertAdjacentElement("afterend", progress);
},
/**
* Returns a string from the downloads stringbundleset, which contains legacy
* strings that are loaded from DTD files instead of properties files. This
* won't be necessary once localization is converted to Fluent (bug 1452637).
*/
string(l10nId) {
@@ -265,38 +271,22 @@ this.DownloadsViewUI.DownloadElementShel
* @param mode
* Either "normal" or "undetermined".
* @param value
* Percentage of the progress bar to display, from 0 to 100.
* @param paused
* True to display the progress bar style for paused downloads.
*/
showProgress(mode, value, paused) {
- // The "undetermined" mode of the progressmeter is implemented with a
- // different element structure, and with support from platform code as well.
- // On Linux only, this mode isn't compatible with the custom styling that we
- // apply separately with the "progress-undetermined" attribute.
- this._downloadProgress.setAttribute("mode",
- AppConstants.platform == "linux" ? "normal" : mode);
if (mode == "undetermined") {
- this._downloadProgress.setAttribute("progress-undetermined", "true");
+ this._downloadProgress.removeAttribute("value");
} else {
- this._downloadProgress.removeAttribute("progress-undetermined");
+ this._downloadProgress.setAttribute("value", value);
}
- this._downloadProgress.setAttribute("value", value);
- if (paused) {
- this._downloadProgress.setAttribute("paused", "true");
- } else {
- this._downloadProgress.removeAttribute("paused");
- }
-
- // Dispatch the ValueChange event for accessibility.
- let event = this.element.ownerDocument.createEvent("Events");
- event.initEvent("ValueChange", true, true);
- this._downloadProgress.dispatchEvent(event);
+ this._downloadProgress.toggleAttribute("paused", !!paused);
},
/**
* Updates the full status line.
*
* @param status
* Status line of the Downloads Panel or the Downloads View.
* @param hoverStatus
--- a/browser/components/downloads/content/downloadsPanel.inc.xul
+++ b/browser/components/downloads/content/downloadsPanel.inc.xul
@@ -123,21 +123,19 @@
orient="horizontal"
onkeydown="DownloadsSummary.onKeyDown(event);"
onclick="DownloadsSummary.onClick(event);">
<image class="downloadTypeIcon" />
<vbox pack="center"
flex="1"
class="downloadContainer">
<description id="downloadsSummaryDescription"/>
- <progressmeter id="downloadsSummaryProgress"
+ <html:progress id="downloadsSummaryProgress"
class="downloadProgress"
- min="0"
- max="100"
- mode="normal" />
+ max="100"/>
<description id="downloadsSummaryDetails"
crop="end"/>
</vbox>
</hbox>
<hbox id="downloadsFooterButtons">
<button id="downloadsHistory"
class="downloadsPanelFooterButton"
label="&downloadsHistory.label;"
--- a/browser/components/pocket/content/Pocket.jsm
+++ b/browser/components/pocket/content/Pocket.jsm
@@ -29,23 +29,18 @@ var Pocket = {
* Functions related to the Pocket panel UI.
*/
onShownInPhotonPageActionPanel(panel, iframe) {
let window = panel.ownerGlobal;
window.pktUI.setPhotonPageActionPanelFrame(iframe);
Pocket._initPanelView(window);
},
- onPanelViewShowing(event) {
- Pocket._initPanelView(event.target.ownerGlobal);
- },
-
_initPanelView(window) {
let document = window.document;
- let iframe = window.pktUI.getPanelFrame();
let libraryButton = document.getElementById("library-button");
if (libraryButton) {
BrowserUtils.setToolbarButtonHeightProperty(libraryButton);
}
let urlToSave = Pocket._urlToSave;
let titleToSave = Pocket._titleToSave;
@@ -54,46 +49,19 @@ var Pocket = {
// ViewShowing fires immediately before it creates the contents,
// in lieu of an AfterViewShowing event, just spin the event loop.
window.setTimeout(function() {
if (urlToSave) {
window.pktUI.tryToSaveUrl(urlToSave, titleToSave);
} else {
window.pktUI.tryToSaveCurrentPage();
}
-
- // pocketPanelDidHide in main.js set iframe to about:blank when it was
- // hidden, make sure we're loading the save panel.
- if (iframe.contentDocument &&
- iframe.contentDocument.readyState == "complete" &&
- iframe.contentDocument.documentURI != "about:blank") {
- window.pktUI.pocketPanelDidShow();
- } else {
- // iframe didn't load yet. This seems to always be the case when in
- // the toolbar panel, but never the case for a subview.
- // XXX this only being fired when it's a _capturing_ listener!
- iframe.addEventListener("load", Pocket.onFrameLoaded, true);
- }
}, 0);
},
- onFrameLoaded(event) {
- let document = event.currentTarget.ownerDocument;
- let window = document.defaultView;
- let iframe = window.pktUI.getPanelFrame();
-
- iframe.removeEventListener("load", Pocket.onFrameLoaded, true);
- window.pktUI.pocketPanelDidShow();
- },
-
- onPanelViewHiding(event) {
- let window = event.target.ownerGlobal;
- window.pktUI.pocketPanelDidHide(event);
- },
-
_urlToSave: null,
_titleToSave: null,
savePage(browser, url, title) {
if (this.pageAction) {
this._urlToSave = url;
this._titleToSave = title;
this.pageAction.doCommand(browser.ownerGlobal);
}
--- a/browser/components/pocket/content/main.js
+++ b/browser/components/pocket/content/main.js
@@ -50,90 +50,37 @@ ChromeUtils.defineModuleGetter(this, "Pr
ChromeUtils.defineModuleGetter(this, "ReaderMode",
"resource://gre/modules/ReaderMode.jsm");
ChromeUtils.defineModuleGetter(this, "pktApi",
"chrome://pocket/content/pktApi.jsm");
var pktUI = (function() {
// -- Initialization (on startup and new windows) -- //
- var _currentPanelDidShow;
- var _currentPanelDidHide;
// Init panel id at 0. The first actual panel id will have the number 1 so
// in case at some point any panel has the id 0 we know there is something
// wrong
var _panelId = 0;
var overflowMenuWidth = 230;
var overflowMenuHeight = 475;
var savePanelWidth = 350;
var savePanelHeights = {collapsed: 153, expanded: 272};
- var _lastAddSucceeded = false;
-
- // -- Event Handling -- //
-
- /**
- * Event handler when Pocket toolbar button is pressed
- */
-
- function pocketPanelDidShow(event) {
- if (_currentPanelDidShow) {
- _currentPanelDidShow(event);
- }
-
- }
-
- function pocketPanelDidHide(event) {
- if (_currentPanelDidHide) {
- _currentPanelDidHide(event);
- }
-
- // clear the panel
- getPanelFrame().setAttribute("src", "about:blank");
-
- if (_lastAddSucceeded) {
- var libraryButton = document.getElementById("library-button");
- if (!Services.prefs.getBoolPref("toolkit.cosmeticAnimations.enabled") ||
- !libraryButton ||
- libraryButton.getAttribute("cui-areatype") == "menu-panel" ||
- libraryButton.getAttribute("overflowedItem") == "true" ||
- !libraryButton.closest("#nav-bar")) {
- return;
- }
- libraryButton.removeAttribute("fade");
- libraryButton.setAttribute("animate", "pocket");
- libraryButton.addEventListener("animationend", onLibraryButtonAnimationEnd);
- }
- }
-
- function onLibraryButtonAnimationEnd(event) {
- let doc = event.target.ownerDocument;
- let libraryButton = doc.getElementById("library-button");
- if (event.animationName.startsWith("library-pocket-animation")) {
- libraryButton.setAttribute("fade", "true");
- } else if (event.animationName == "library-pocket-fade") {
- libraryButton.removeEventListener("animationend", onLibraryButtonAnimationEnd);
- libraryButton.removeAttribute("animate");
- libraryButton.removeAttribute("fade");
- }
- }
-
// -- Communication to API -- //
/**
* Either save or attempt to log the user in
*/
function tryToSaveCurrentPage() {
tryToSaveUrl(getCurrentUrl(), getCurrentTitle());
}
function tryToSaveUrl(url, title) {
-
// If the user is logged in, go ahead and save the current page
if (pktApi.isUserLoggedIn()) {
saveAndShowConfirmation(url, title);
return;
}
// If the user is not logged in, show the logged-out state to prompt them to authenticate
showSignUp();
@@ -190,19 +137,16 @@ var pktUI = (function() {
+ "&variant="
+ variant
+ "&controlvariant="
+ controlvariant
+ "&inoverflowmenu="
+ inOverflowMenu
+ "&locale="
+ getUILocale(), {
- onShow() {
- },
- onHide: panelDidHide,
width: inOverflowMenu ? overflowMenuWidth : 300,
height: startheight,
});
});
}
/**
* Show the logged-out state / sign-up panel
@@ -226,17 +170,16 @@ var pktUI = (function() {
var panelId = showPanel("about:pocket-saved?pockethost="
+ Services.prefs.getCharPref("extensions.pocket.site")
+ "&premiumStatus=" + (pktApi.isPremiumUser() ? "1" : "0")
+ "&fxasignedin=" + ((typeof userdata == "object" && userdata !== null) ? "1" : "0")
+ "&inoverflowmenu=" + inOverflowMenu
+ "&locale=" + getUILocale(), {
onShow() {
var saveLinkMessageId = "saveLink";
- _lastAddSucceeded = false;
getPanelFrame().setAttribute("itemAdded", "false");
// Send error message for invalid url
if (!isValidURL) {
// TODO: Pass key for localized error in error object
let error = {
message: "Only links can be saved",
localizedKey: "onlylinkssaved",
@@ -265,17 +208,16 @@ var pktUI = (function() {
var successResponse = {
status: "success",
accountState,
displayName,
item,
ho2,
};
pktUIMessaging.sendMessageToPanel(panelId, saveLinkMessageId, successResponse);
- _lastAddSucceeded = true;
getPanelFrame().setAttribute("itemAdded", "true");
},
error(error, request) {
// If user is not authorized show singup page
if (request.status === 401) {
showSignUp();
return;
}
@@ -293,52 +235,52 @@ var pktUI = (function() {
// Add title if given
if (typeof title !== "undefined") {
options.title = title;
}
// Send the link
pktApi.addLink(url, options);
},
- onHide: panelDidHide,
width: inOverflowMenu ? overflowMenuWidth : savePanelWidth,
height: startheight,
});
});
}
/**
* Open a generic panel
*/
function showPanel(url, options) {
-
// Add new panel id
_panelId += 1;
url += ("&panelId=" + _panelId);
// We don't have to hide and show the panel again if it's already shown
// as if the user tries to click again on the toolbar button the overlay
// will close instead of the button will be clicked
var iframe = getPanelFrame();
+ options.onShow = options.onShow || (() => {});
// Register event handlers
registerEventMessages();
// Load the iframe
iframe.setAttribute("src", url);
- // Uncomment to leave panel open -- for debugging
- // panel.setAttribute('noautohide', true);
- // panel.setAttribute('consumeoutsideclicks', false);
- //
-
- // For some reason setting onpopupshown and onpopuphidden on the panel directly didn't work, so
- // do it this hacky way for now
- _currentPanelDidShow = options.onShow;
- _currentPanelDidHide = options.onHide;
+ if (iframe.contentDocument &&
+ iframe.contentDocument.readyState == "complete" &&
+ iframe.contentDocument.documentURI != "about:blank") {
+ options.onShow();
+ } else {
+ // iframe didn't load yet. This seems to always be the case when in
+ // the toolbar panel, but never the case for a subview.
+ // XXX this only being fired when it's a _capturing_ listener!
+ iframe.addEventListener("load", options.onShow, { once: true, capture: true });
+ }
resizePanel({
width: options.width,
height: options.height,
});
return _panelId;
}
@@ -354,25 +296,16 @@ var pktUI = (function() {
var iframe = getPanelFrame();
// Set an explicit size, panel will adapt.
iframe.style.width = options.width + "px";
iframe.style.height = options.height + "px";
}
/**
- * Called when the signup and saved panel was hidden
- */
- function panelDidHide() {
- // clear the onShow and onHide values
- _currentPanelDidShow = null;
- _currentPanelDidHide = null;
- }
-
- /**
* Register all of the messages needed for the panels
*/
function registerEventMessages() {
var iframe = getPanelFrame();
// Only register the messages once
var didInitAttributeKey = "did_init";
var didInitMessageListener = iframe.getAttribute(didInitAttributeKey);
@@ -512,17 +445,16 @@ var pktUI = (function() {
// Based on clicking "remove page" CTA, and passed unique item id, remove the item
var _deleteItemMessageId = "deleteItem";
pktUIMessaging.addMessageListener(iframe, _deleteItemMessageId, function(panelId, data) {
pktApi.deleteItem(data.itemId, {
success(data, response) {
var successResponse = {status: "success"};
pktUIMessaging.sendResponseMessageToPanel(panelId, _deleteItemMessageId, successResponse);
- _lastAddSucceeded = false;
getPanelFrame().setAttribute("itemAdded", "false");
},
error(error, response) {
pktUIMessaging.sendErrorResponseMessageToPanel(panelId, _deleteItemMessageId, error);
},
});
});
@@ -631,19 +563,16 @@ var pktUI = (function() {
* Public functions
*/
return {
setPhotonPageActionPanelFrame,
getPanelFrame,
openTabWithUrl,
- pocketPanelDidShow,
- pocketPanelDidHide,
-
tryToSaveUrl,
tryToSaveCurrentPage,
};
}());
// -- Communication to Background -- //
// https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Interaction_between_privileged_and_non-privileged_pages
var pktUIMessaging = (function() {
--- a/browser/components/preferences/browserLanguages.js
+++ b/browser/components/preferences/browserLanguages.js
@@ -5,16 +5,34 @@
/* import-globals-from ../../../toolkit/content/preferencesBindings.js */
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "AddonManager",
"resource://gre/modules/AddonManager.jsm");
ChromeUtils.defineModuleGetter(this, "AddonRepository",
"resource://gre/modules/addons/AddonRepository.jsm");
+ChromeUtils.defineModuleGetter(this, "RemoteSettings",
+ "resource://services-settings/remote-settings.js");
+
+async function installFromUrl(url, hash) {
+ let install = await AddonManager.getInstallForURL(
+ url, "application/x-xpinstall", hash);
+ return install.install();
+}
+
+async function dictionaryIdsForLocale(locale) {
+ let entries = await RemoteSettings("language-dictionaries").get({
+ filters: {id: locale},
+ });
+ if (entries.length > 0) {
+ return entries[0].dictionaries;
+ }
+ return [];
+}
class OrderedListBox {
constructor({richlistbox, upButton, downButton, removeButton, onRemove}) {
this.richlistbox = richlistbox;
this.upButton = upButton;
this.downButton = downButton;
this.removeButton = removeButton;
this.onRemove = onRemove;
@@ -389,47 +407,71 @@ var gBrowserLanguagesDialog = {
items.push({
label: await document.l10n.formatValue("browser-languages-search"),
value: "search",
});
this._availableLocales.setItems(items);
},
async availableLanguageSelected(item) {
- let available = new Set(Services.locale.availableLocales);
-
- if (available.has(item.value)) {
- this._requestedLocales.addItem(item);
- if (available.size == this._requestedLocales.items.length) {
- // Remove the installed label, they're all installed.
- this._availableLocales.items.shift();
- this._availableLocales.setItems(this._availableLocales.items);
- }
+ if (Services.locale.availableLocales.includes(item.value)) {
+ this.requestLocalLanguage(item);
} else if (this.availableLangpacks.has(item.value)) {
- this._availableLocales.disableWithMessageId("browser-languages-downloading");
-
- let {url, hash} = this.availableLangpacks.get(item.value);
- let install = await AddonManager.getInstallForURL(
- url, "application/x-xpinstall", hash);
-
- try {
- await install.install();
- } catch (e) {
- this.showError();
- return;
- }
-
- item.installed = true;
- this._requestedLocales.addItem(item);
- this._availableLocales.enableWithMessageId("browser-languages-select-language");
+ await this.requestRemoteLanguage(item);
} else {
this.showError();
}
},
+ requestLocalLanguage(item, available) {
+ this._requestedLocales.addItem(item);
+ let requestedCount = this._requestedLocales.items.length;
+ let availableCount = Services.locale.availableLocales.length;
+ if (requestedCount == availableCount) {
+ // Remove the installed label, they're all installed.
+ this._availableLocales.items.shift();
+ this._availableLocales.setItems(this._availableLocales.items);
+ }
+ },
+
+ async requestRemoteLanguage(item) {
+ this._availableLocales.disableWithMessageId(
+ "browser-languages-downloading");
+
+ let {url, hash} = this.availableLangpacks.get(item.value);
+
+ try {
+ await installFromUrl(url, hash);
+ } catch (e) {
+ this.showError();
+ return;
+ }
+
+ item.installed = true;
+ this._requestedLocales.addItem(item);
+ this._availableLocales.enableWithMessageId(
+ "browser-languages-select-language");
+
+ // This is an async task that will install the recommended dictionaries for
+ // this locale. This will fail silently at least until a management UI is
+ // added in bug 1493705.
+ this.installDictionariesForLanguage(item.value);
+ },
+
+ async installDictionariesForLanguage(locale) {
+ try {
+ let ids = await dictionaryIdsForLocale(locale);
+ let addonInfos = await AddonRepository.getAddonsByIDs(ids);
+ await Promise.all(addonInfos.map(
+ info => installFromUrl(info.sourceURI.spec)));
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
showError() {
document.querySelectorAll(".warning-message-separator")
.forEach(separator => separator.classList.add("thin"));
document.getElementById("warning-message").hidden = false;
this._availableLocales.enableWithMessageId("browser-languages-select-language");
},
hideError() {
--- a/browser/components/preferences/in-content/extensionControlled.js
+++ b/browser/components/preferences/in-content/extensionControlled.js
@@ -39,16 +39,17 @@ const API_PROXY_PREFS = [
"network.proxy.autoconfig_url",
"signon.autologin.proxy",
];
let extensionControlledContentIds = {
"privacy.containers": "browserContainersExtensionContent",
"homepage_override": "browserHomePageExtensionContent",
"newTabURL": "browserNewTabExtensionContent",
+ "webNotificationsDisabled": "browserNotificationsPermissionExtensionContent",
"defaultSearch": "browserDefaultSearchExtensionContent",
"proxy.settings": "proxyExtensionContent",
get "websites.trackingProtectionMode"() {
return {
button: contentBlockingUiEnabled ?
"contentBlockingDisableTrackingProtectionExtension" :
"trackingProtectionExtensionContentButton",
section: contentBlockingUiEnabled ?
@@ -56,16 +57,17 @@ let extensionControlledContentIds = {
"trackingProtectionExtensionContentLabel",
};
},
};
const extensionControlledL10nKeys = {
"homepage_override": "homepage-override",
"newTabURL": "new-tab-url",
+ "webNotificationsDisabled": "web-notifications",
"defaultSearch": "default-search",
"privacy.containers": "privacy-containers",
"websites.trackingProtectionMode": contentBlockingUiEnabled ?
"websites-content-blocking-all-trackers" :
"websites-tracking-protection-mode",
"proxy.settings": "proxy-config",
};
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3113d1fa5da0482256cef2fd3edf78ad2f6e7bc2
GIT binary patch
literal 963
zc$^FHW@Zs#U|`^25U%v`)pC+`E?{I}U}t7v;AW6v$jmD)NzBR7D@sWV4dG;9cKd%I
zx)g*fxEUB(z5-Q(HRn$9FK#jrIQqVhtLyoM56iW9j+Gtzc<1H9@1jj<T%40zR=xeQ
zTYRxg&gstD_umh_YpW^E$$H<cd3I^UuSADGqH~|Df8Bds+DZTKUc=nBj}Hqt_zD%S
z^0jBqN)mdg;k0Va1UpV<ca<+!=2tvF&Sre=PO9|Jldq-TFbXPirk%C4{hBF%>bj43
z*Y;)khnm;EIdkd#loRU~_HgGeSQp6L6z(G}{VI9Q$qLWgzg9juYRTPYEb9>IweWcS
znUAd{D_<YjGT*$z!$LW5%Z3XH7Mp~1Mc!Xo(HHq3%KHACD7W;^uWxo2Zwr#j71^@=
zgKyE4hPI31k;QwrcdVTot-<D|wQJ&~UCwVCK5g6aQ8T%+s8L?vlUdA0FFD&5H|B<0
zhM(_jGu1z^<K}z))C+gc?T^@~V7T(xiVltiDbqqU?h3UXl1TE1wCWc>`Tg#__L9R#
zZ<n6#XWV$gW0(6wix(%867p|d(D--x<x{JFv)_o%dHgSY`DFP3V5D&|fPgYE(pqfH
zLL-4}5EceTT1sYeNoIatVo_#lv3^00USb*~@{)iNmx392!LA_+p27YW7N(ZEMqD95
zkqU{K`KeWTMditr#RVnVxhb20v9&QhDdW|)XP@_{ecV%4SmEgD?;7P76z&`u91!B|
z>#{i1$7`XxlgFuL$1bmTySOGSFoFx=N)4bZ*%!XL%LHUI;cz984|ZiX(1qEUu1o|P
z$H*kdj4Q25fGq-Y8J0AHSSTr%6`FEUjf5D8D;$vx+zO<yhAJy0RPh*$>2+j-qnR)b
Z1_uNyBp@&hW@Q6uW(Gl^;3|-b3;>KuQZoPm
--- a/browser/components/preferences/in-content/tests/browser.ini
+++ b/browser/components/preferences/in-content/tests/browser.ini
@@ -1,15 +1,16 @@
[DEFAULT]
prefs =
extensions.formautofill.available='on'
extensions.formautofill.creditCards.available=true
support-files =
head.js
privacypane_tests_perwindow.js
+ addons/pl-dictionary.xpi
addons/set_homepage.xpi
addons/set_newtab.xpi
[browser_applications_selection.js]
[browser_advanced_update.js]
skip-if = !updater
[browser_basic_rebuild_fonts_test.js]
[browser_bug410900.js]
--- a/browser/components/preferences/in-content/tests/browser_browser_languages_subdialog.js
+++ b/browser/components/preferences/in-content/tests/browser_browser_languages_subdialog.js
@@ -1,17 +1,19 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this);
ChromeUtils.import("resource://gre/modules/Services.jsm");
+
AddonTestUtils.initMochitest(this);
const BROWSER_LANGUAGES_URL = "chrome://browser/content/preferences/browserLanguages.xul";
+const DICTIONARY_ID_PL = "pl@dictionaries.addons.mozilla.org";
function getManifestData(locale) {
return {
langpack_id: locale,
name: `${locale} Language Pack`,
description: `${locale} Language pack`,
languages: {
[locale]: {
@@ -74,16 +76,55 @@ async function createLanguageToolsFile()
let files = {[filename]: {results}};
let tempdir = AddonTestUtils.tempDir.clone();
let dir = await AddonTestUtils.promiseWriteFilesToDir(tempdir.path, files);
dir.append(filename);
return dir;
}
+async function createDictionaryBrowseResults() {
+ let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
+ let dictionaryPath = testDir + "/addons/pl-dictionary.xpi";
+ let filename = "dictionaries.json";
+ let response = {
+ page_size: 25,
+ page_count: 1,
+ count: 1,
+ results: [{
+ current_version: {
+ id: 1823648,
+ compatibility: {
+ firefox: {max: "9999", min: "4.0"},
+ },
+ files: [{
+ platform: "all",
+ url: dictionaryPath,
+ }],
+ version: "1.0.20160228",
+ },
+ default_locale: "pl",
+ description: "Polish spell-check",
+ guid: DICTIONARY_ID_PL,
+ name: "Polish Dictionary",
+ slug: "polish-spellchecker-dictionary",
+ status: "public",
+ summary: "Polish dictionary",
+ type: "dictionary",
+ }],
+ };
+
+ let files = {[filename]: response};
+ let dir = await AddonTestUtils.promiseWriteFilesToDir(
+ AddonTestUtils.tempDir.path, files);
+ dir.append(filename);
+
+ return dir;
+}
+
function assertLocaleOrder(list, locales) {
is(list.itemCount, locales.split(",").length,
"The right number of locales are requested");
is(Array.from(list.children).map(child => child.value).join(","),
locales, "The requested locales are in order");
}
function assertAvailableLocales(list, locales) {
@@ -240,22 +281,26 @@ add_task(async function testAddAndRemove
});
add_task(async function testInstallFromAMO() {
let langpacks = await AddonManager.getAddonsByTypes(["locale"]);
is(langpacks.length, 0, "There are no langpacks installed");
let langpacksFile = await createLanguageToolsFile();
let langpacksUrl = Services.io.newFileURI(langpacksFile).spec;
+ let dictionaryBrowseFile = await createDictionaryBrowseResults();
+ let browseApiEndpoint = Services.io.newFileURI(dictionaryBrowseFile).spec;
+
await SpecialPowers.pushPrefEnv({
set: [
["intl.multilingual.enabled", true],
["intl.locale.requested", "en-US"],
["extensions.getAddons.langpacks.url", langpacksUrl],
["extensions.langpacks.signatures.required", false],
+ ["extensions.getAddons.get.url", browseApiEndpoint],
],
});
await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
let doc = gBrowser.contentDocument;
let messageBar = doc.getElementById("confirmBrowserLanguage");
is(messageBar.hidden, true, "The message bar is hidden at first");
@@ -272,31 +317,46 @@ add_task(async function testInstallFromA
}
// The initial order is set by the pref.
assertLocaleOrder(requested, "en-US");
assertAvailableLocales(available, ["fr", "he", "pl"]);
is(Services.locale.availableLocales.join(","),
"en-US", "There is only one installed locale");
+ // Verify that there are no extra dictionaries.
+ let dicts = await AddonManager.getAddonsByTypes(["dictionary"]);
+ is(dicts.length, 0, "There are no installed dictionaries");
+
// Add Polish, this will install the langpack.
requestLocale("pl", available, dialogDoc);
// Wait for the langpack to install and be added to the list.
let requestedLocales = dialogDoc.getElementById("requestedLocales");
await waitForMutation(
requestedLocales,
{childList: true},
target => requestedLocales.itemCount == 2);
// Verify the list is correct.
assertLocaleOrder(requested, "pl,en-US");
assertAvailableLocales(available, ["fr", "he"]);
is(Services.locale.availableLocales.sort().join(","),
"en-US,pl", "Polish is now installed");
- // Uninstall the langpack.
- langpacks = await AddonManager.getAddonsByTypes(["locale"]);
- is(langpacks.length, 1, "There is one langpacks installed");
- await Promise.all(langpacks.map(pack => pack.uninstall()));
+ await BrowserTestUtils.waitForCondition(async () => {
+ let newDicts = await AddonManager.getAddonsByTypes(["dictionary"]);
+ let done = newDicts.length != 0;
+
+ if (done) {
+ is(newDicts[0].id, DICTIONARY_ID_PL, "The polish dictionary was installed");
+ }
+
+ return done;
+ });
+
+ // Uninstall the langpack and dictionary.
+ let installs = await AddonManager.getAddonsByTypes(["locale", "dictionary"]);
+ is(installs.length, 2, "There is one langpack and one dictionary installed");
+ await Promise.all(installs.map(item => item.uninstall()));
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
--- a/browser/components/preferences/in-content/tests/browser_extension_controlled.js
+++ b/browser/components/preferences/in-content/tests/browser_extension_controlled.js
@@ -6,16 +6,18 @@ ChromeUtils.defineModuleGetter(this, "Ex
"resource://gre/modules/ExtensionSettingsStore.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
"@mozilla.org/browser/aboutnewtab-service;1",
"nsIAboutNewTabService");
XPCOMUtils.defineLazyPreferenceGetter(this, "proxyType", PROXY_PREF);
const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
const CHROME_URL_ROOT = TEST_DIR + "/";
+const PERMISSIONS_URL = "chrome://browser/content/preferences/sitePermissions.xul";
+let sitePermissionsDialog;
function getSupportsFile(path) {
let cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
.getService(Ci.nsIChromeRegistry);
let uri = Services.io.newURI(CHROME_URL_ROOT + path);
let fileurl = cr.convertChromeURL(uri);
return fileurl.QueryInterface(Ci.nsIFileURL);
}
@@ -63,16 +65,29 @@ function waitForEnableMessage(messageId,
function waitForMessageContent(messageId, l10nId, doc) {
return waitForMessageChange(
getElement(messageId, doc),
target => doc.l10n.getAttributes(target).id === l10nId,
{ childList: true });
}
+async function openNotificationsPermissionDialog() {
+ let dialogOpened = promiseLoadSubDialog(PERMISSIONS_URL);
+
+ await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+ let doc = content.document;
+ let settingsButton = doc.getElementById("notificationSettingsButton");
+ settingsButton.click();
+ });
+
+ sitePermissionsDialog = await dialogOpened;
+ await sitePermissionsDialog.document.mozSubdialogReady;
+}
+
add_task(async function testExtensionControlledHomepage() {
await openPreferencesViaOpenPreferencesAPI("paneHome", {leaveOpen: true});
// eslint-disable-next-line mozilla/no-cpows-in-tests
let doc = gBrowser.contentDocument;
is(gBrowser.currentURI.spec, "about:preferences#home",
"#home should be in the URI for about:preferences");
let homepagePref = () => Services.prefs.getCharPref("browser.startup.homepage");
let originalHomepagePref = homepagePref();
@@ -334,16 +349,85 @@ add_task(async function testExtensionCon
is(controlledContent.hidden, true, "The extension controlled row is shown");
// Cleanup the tab and add-on.
BrowserTestUtils.removeTab(gBrowser.selectedTab);
let addon = await AddonManager.getAddonByID("@set_newtab");
await addon.uninstall();
});
+add_task(async function testExtensionControlledWebNotificationsPermission() {
+ let manifest = {
+ manifest_version: 2,
+ name: "TestExtension",
+ version: "1.0",
+ description: "Testing WebNotificationsDisable",
+ applications: {gecko: {id: "@web_notifications_disable"}},
+ permissions: [
+ "browserSettings",
+ ],
+ browser_action: {
+ default_title: "Testing",
+ },
+ };
+
+ await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
+ await openNotificationsPermissionDialog();
+
+ let doc = sitePermissionsDialog.document;
+ let extensionControlledContent = doc.getElementById("browserNotificationsPermissionExtensionContent");
+
+ // Test that extension content is initially hidden.
+ ok(extensionControlledContent.hidden, "Extension content is initially hidden");
+
+ // Install an extension that will disable web notifications permission.
+ let messageShown = waitForMessageShown("browserNotificationsPermissionExtensionContent", doc);
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest,
+ useAddonManager: "permanent",
+ background() {
+ browser.browserSettings.webNotificationsDisabled.set({value: true});
+ browser.test.sendMessage("load-extension");
+ },
+ });
+ await extension.startup();
+ await extension.awaitMessage("load-extension");
+ await messageShown;
+
+ let controlledDesc = extensionControlledContent.querySelector("description");
+ Assert.deepEqual(doc.l10n.getAttributes(controlledDesc), {
+ id: "extension-controlled-web-notifications",
+ args: {
+ name: "TestExtension",
+ },
+ }, "The user is notified that an extension is controlling the web notifications permission");
+ is(extensionControlledContent.hidden, false, "The extension controlled row is not hidden");
+
+ // Disable the extension.
+ doc.getElementById("disableNotificationsPermissionExtension").click();
+
+ // Verify the user is notified how to enable the extension.
+ await waitForEnableMessage(extensionControlledContent.id, doc);
+ is(doc.l10n.getAttributes(controlledDesc.querySelector("label")).id,
+ "extension-controlled-enable",
+ "The user is notified of how to enable the extension again");
+
+ // Verify the enable message can be dismissed.
+ let hidden = waitForMessageHidden(extensionControlledContent.id, doc);
+ let dismissButton = controlledDesc.querySelector("image:last-of-type");
+ dismissButton.click();
+ await hidden;
+
+ // Verify that the extension controlled content in hidden again.
+ is(extensionControlledContent.hidden, true, "The extension controlled row is now hidden");
+
+ await extension.unload();
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
add_task(async function testExtensionControlledDefaultSearch() {
await openPreferencesViaOpenPreferencesAPI("paneSearch", {leaveOpen: true});
let doc = gBrowser.contentDocument;
let extensionId = "@set_default_search";
let manifest = {
manifest_version: 2,
name: "set_default_search",
applications: {gecko: {id: extensionId}},
--- a/browser/components/preferences/sitePermissions.css
+++ b/browser/components/preferences/sitePermissions.css
@@ -18,18 +18,22 @@
min-height: 35px;
}
.website-status {
margin: 1px;
margin-inline-end: 5px;
}
+#browserNotificationsPermissionExtensionContent,
#permissionsDisableDescription {
margin-inline-start: 32px;
+}
+
+#permissionsDisableDescription {
color: #737373;
line-height: 110%;
}
#permissionsDisableCheckbox {
margin-inline-start: 4px;
padding-top: 10px;
}
--- a/browser/components/preferences/sitePermissions.js
+++ b/browser/components/preferences/sitePermissions.js
@@ -1,12 +1,14 @@
/* 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/. */
+/* import-globals-from in-content/extensionControlled.js */
+
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
ChromeUtils.import("resource:///modules/SitePermissions.jsm");
const sitePermissionsL10n = {
"desktop-notification": {
window: "permissions-site-notification-window",
description: "permissions-site-notification-desc",
@@ -37,16 +39,18 @@ function Permission(principal, type, cap
this.principal = principal;
this.origin = principal.origin;
this.type = type;
this.capability = capability;
this.l10nId = l10nId;
}
const PERMISSION_STATES = [SitePermissions.ALLOW, SitePermissions.BLOCK, SitePermissions.PROMPT];
+const NOTIFICATIONS_PERMISSION_OVERRIDE_KEY = "webNotificationsDisabled";
+const NOTIFICATIONS_PERMISSION_PREF = "permissions.default.desktop-notification";
var gSitePermissionsManager = {
_type: "",
_isObserving: false,
_permissions: new Map(),
_permissionsToChange: new Map(),
_permissionsToDelete: new Map(),
_list: null,
@@ -69,52 +73,38 @@ var gSitePermissionsManager = {
}
this._type = params.permissionType;
this._list = document.getElementById("permissionsBox");
this._removeButton = document.getElementById("removePermission");
this._removeAllButton = document.getElementById("removeAllPermissions");
this._searchBox = document.getElementById("searchBox");
this._checkbox = document.getElementById("permissionsDisableCheckbox");
+ this._disableExtensionButton = document.getElementById("disableNotificationsPermissionExtension");
+ this._permissionsDisableDescription = document.getElementById("permissionsDisableDescription");
- let permissionsDisableDescription = document.getElementById("permissionsDisableDescription");
let permissionsText = document.getElementById("permissionsText");
let l10n = sitePermissionsL10n[this._type];
document.l10n.setAttributes(permissionsText, l10n.description);
document.l10n.setAttributes(this._checkbox, l10n.disableLabel);
- document.l10n.setAttributes(permissionsDisableDescription, l10n.disableDescription);
+ document.l10n.setAttributes(this._permissionsDisableDescription, l10n.disableDescription);
document.l10n.setAttributes(document.documentElement, l10n.window);
await document.l10n.translateElements([
permissionsText,
this._checkbox,
- permissionsDisableDescription,
+ this._permissionsDisableDescription,
document.documentElement,
]);
- // Initialize the checkbox state.
+ // Initialize the checkbox state and handle showing notification permission UI
+ // when it is disabled by an extension.
this._defaultPermissionStatePrefName = "permissions.default." + this._type;
- let pref = Services.prefs.getPrefType(this._defaultPermissionStatePrefName);
- if (pref != Services.prefs.PREF_INVALID) {
- this._currentDefaultPermissionsState = Services.prefs.getIntPref(this._defaultPermissionStatePrefName);
- }
-
- if (this._currentDefaultPermissionsState === null) {
- this._checkbox.setAttribute("hidden", true);
- permissionsDisableDescription.setAttribute("hidden", true);
- } else if (this._currentDefaultPermissionsState == SitePermissions.BLOCK) {
- this._checkbox.checked = true;
- } else {
- this._checkbox.checked = false;
- }
-
- if (Services.prefs.prefIsLocked(this._defaultPermissionStatePrefName)) {
- this._checkbox.disabled = true;
- }
+ this._watchPermissionPrefChange();
this._loadPermissions();
this.buildPermissionsList();
this._searchBox.focus();
},
uninit() {
@@ -150,16 +140,78 @@ var gSitePermissionsManager = {
_handleCapabilityChange(perm) {
let permissionlistitem = document.getElementsByAttribute("origin", perm.origin)[0];
let menulist = permissionlistitem.getElementsByTagName("menulist")[0];
menulist.selectedItem =
menulist.getElementsByAttribute("value", perm.capability)[0];
},
+ _handleCheckboxUIUpdates() {
+ let pref = Services.prefs.getPrefType(this._defaultPermissionStatePrefName);
+ if (pref != Services.prefs.PREF_INVALID) {
+ this._currentDefaultPermissionsState = Services.prefs.getIntPref(this._defaultPermissionStatePrefName);
+ }
+
+ if (this._currentDefaultPermissionsState === null) {
+ this._checkbox.setAttribute("hidden", true);
+ this._permissionsDisableDescription.setAttribute("hidden", true);
+ } else if (this._currentDefaultPermissionsState == SitePermissions.BLOCK) {
+ this._checkbox.checked = true;
+ } else {
+ this._checkbox.checked = false;
+ }
+
+ if (Services.prefs.prefIsLocked(this._defaultPermissionStatePrefName)) {
+ this._checkbox.disabled = true;
+ }
+ },
+
+ /**
+ * Listen for changes to the permissions.default.* pref and make
+ * necessary changes to the UI.
+ */
+ _watchPermissionPrefChange() {
+ this._handleCheckboxUIUpdates();
+
+ if (this._type == "desktop-notification") {
+ this._handleWebNotificationsDisable();
+
+ this._disableExtensionButton.addEventListener(
+ "command",
+ makeDisableControllingExtension(PREF_SETTING_TYPE, NOTIFICATIONS_PERMISSION_OVERRIDE_KEY)
+ );
+ }
+
+ let observer = () => {
+ this._handleCheckboxUIUpdates();
+ if (this._type == "desktop-notification") {
+ this._handleWebNotificationsDisable();
+ }
+ };
+ Services.prefs.addObserver(this._defaultPermissionStatePrefName, observer);
+ window.addEventListener("unload", () => {
+ Services.prefs.removeObserver(this._defaultPermissionStatePrefName, observer);
+ });
+ },
+
+ /**
+ * Handles the UI update for web notifications disable by extensions.
+ */
+ async _handleWebNotificationsDisable() {
+ let prefLocked = Services.prefs.prefIsLocked(NOTIFICATIONS_PERMISSION_PREF);
+ if (prefLocked) {
+ // An extension can't control these settings if they're locked.
+ hideControllingExtension(NOTIFICATIONS_PERMISSION_OVERRIDE_KEY);
+ } else {
+ let isControlled = await handleControllingExtension(PREF_SETTING_TYPE, NOTIFICATIONS_PERMISSION_OVERRIDE_KEY);
+ this._checkbox.disabled = isControlled;
+ }
+ },
+
_getCapabilityString(capability) {
let stringKey = null;
switch (capability) {
case Services.perms.ALLOW_ACTION:
stringKey = "permissions-capabilities-allow";
break;
case Services.perms.DENY_ACTION:
stringKey = "permissions-capabilities-block";
--- a/browser/components/preferences/sitePermissions.xul
+++ b/browser/components/preferences/sitePermissions.xul
@@ -14,20 +14,22 @@
data-l10n-id="permissions-window"
data-l10n-attrs="title, style"
onload="gSitePermissionsManager.onLoad();"
onunload="gSitePermissionsManager.uninit();"
persist="screenX screenY width height"
onkeypress="gSitePermissionsManager.onWindowKeyPress(event);">
<linkset>
+ <link rel="localization" href="browser/preferences/preferences.ftl"/>
<link rel="localization" href="browser/preferences/permissions.ftl"/>
</linkset>
<script src="chrome://browser/content/preferences/sitePermissions.js"/>
+ <script type="application/javascript" src="chrome://browser/content/preferences/in-content/extensionControlled.js"/>
<keyset>
<key data-l10n-id="permissions-close-key" modifiers="accel" oncommand="window.close();"/>
</keyset>
<vbox class="contentPane largeDialogContainer" flex="1">
<description id="permissionsText" control="url"/>
<separator class="thin"/>
@@ -58,16 +60,23 @@
data-l10n-id="permissions-remove-all"
icon="clear"
oncommand="gSitePermissionsManager.onAllPermissionsDelete();"/>
</hbox>
<spacer flex="1"/>
<checkbox id="permissionsDisableCheckbox"/>
<description id="permissionsDisableDescription"/>
<spacer flex="1"/>
+ <hbox id="browserNotificationsPermissionExtensionContent"
+ class="extension-controlled" align="center" hidden="true">
+ <description control="disableNotificationsPermissionExtension" flex="1"/>
+ <button id="disableNotificationsPermissionExtension"
+ class="extension-controlled-button accessory-button"
+ data-l10n-id="disable-extension"/>
+ </hbox>
<hbox class="actionButtons" align="right" flex="1">
<button oncommand="close();" icon="close" id="cancel"
data-l10n-id="permissions-button-cancel" />
<button id="btnApplyChanges" oncommand="gSitePermissionsManager.onApplyChanges();" icon="save"
data-l10n-id="permissions-button-ok" />
</hbox>
</vbox>
</window>
--- a/browser/config/mozconfigs/win32/mingwclang
+++ b/browser/config/mozconfigs/win32/mingwclang
@@ -49,17 +49,17 @@ HOST_CXX="$TOOLTOOL_DIR/clang/bin/clang+
CC="$TOOLTOOL_DIR/clang/bin/i686-w64-mingw32-clang"
CXX="$TOOLTOOL_DIR/clang/bin/i686-w64-mingw32-clang++"
CXXFLAGS="-fms-extensions"
CPP="$TOOLTOOL_DIR/clang/bin/i686-w64-mingw32-clang -E"
AR=llvm-ar
RANLIB=llvm-ranlib
# For Stylo
-BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/7.0.0/include -I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include"
+BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/8.0.0/include -I$TOOLTOOL_DIR/clang/i686-w64-mingw32/include"
# Since we use windres from binutils without the rest of tools (like cpp), we need to
# explicitly specify clang as a preprocessor.
WINDRES="i686-w64-mingw32-windres --preprocessor=\"$CPP -xc\" -DRC_INVOKED"
# Bug 1471698 - Work around binutils corrupting mingw clang binaries.
LDFLAGS="-Wl,-S"
STRIP=/bin/true
--- a/browser/config/mozconfigs/win64/mingwclang
+++ b/browser/config/mozconfigs/win64/mingwclang
@@ -49,17 +49,17 @@ HOST_CXX="$TOOLTOOL_DIR/clang/bin/clang+
CC="$TOOLTOOL_DIR/clang/bin/x86_64-w64-mingw32-clang"
CXX="$TOOLTOOL_DIR/clang/bin/x86_64-w64-mingw32-clang++"
CXXFLAGS="-fms-extensions"
CPP="$TOOLTOOL_DIR/clang/bin/x86_64-w64-mingw32-clang -E"
AR=llvm-ar
RANLIB=llvm-ranlib
# For Stylo
-BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/7.0.0/include -I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include"
+BINDGEN_CFLAGS="-I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include/c++/v1 -I$TOOLTOOL_DIR/clang/lib/clang/8.0.0/include -I$TOOLTOOL_DIR/clang/x86_64-w64-mingw32/include"
# Since we use windres from binutils without the rest of tools (like cpp), we need to
# explicitly specify clang as a preprocessor.
WINDRES="x86_64-w64-mingw32-windres --preprocessor=\"$CPP -xc\" -DRC_INVOKED"
# Bug 1471698 - Work around binutils corrupting mingw clang binaries.
LDFLAGS="-Wl,-S"
STRIP=/bin/true
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat/aboutConfigPrefs.js
@@ -0,0 +1,46 @@
+/* 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";
+
+/* global ExtensionAPI, ExtensionCommon */
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+this.aboutConfigPrefs = class extends ExtensionAPI {
+ getAPI(context) {
+ const EventManager = ExtensionCommon.EventManager;
+ const extensionIDBase = context.extension.id.split("@")[0];
+ const extensionPrefNameBase = `extensions.${extensionIDBase}.`;
+
+ return {
+ aboutConfigPrefs: {
+ onPrefChange: new EventManager({
+ context,
+ name: "aboutConfigPrefs.onUAOverridesPrefChange",
+ register: (fire, name) => {
+ const prefName = `${extensionPrefNameBase}${name}`;
+ const callback = () => {
+ fire.async(name).catch(() => {}); // ignore Message Manager disconnects
+ };
+ Services.prefs.addObserver(prefName, callback);
+ return () => {
+ Services.prefs.removeObserver(prefName, callback);
+ };
+ },
+ }).api(),
+ async getPref(name) {
+ try {
+ return Services.prefs.getBoolPref(`${extensionPrefNameBase}${name}`);
+ } catch (_) {
+ return undefined;
+ }
+ },
+ async setPref(name, value) {
+ Services.prefs.setBoolPref(`${extensionPrefNameBase}${name}`, value);
+ },
+ },
+ };
+ }
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat/aboutConfigPrefs.json
@@ -0,0 +1,53 @@
+[
+ {
+ "namespace": "aboutConfigPrefs",
+ "description": "experimental API extension to allow access to about:config preferences",
+ "events": [
+ {
+ "name": "onPrefChange",
+ "type": "function",
+ "parameters": [{
+ "name": "name",
+ "type": "string",
+ "description": "The preference which changed"
+ }],
+ "extraParameters": [{
+ "name": "name",
+ "type": "string",
+ "description": "The preference to monitor"
+ }]
+ }
+ ],
+ "functions": [
+ {
+ "name": "getPref",
+ "type": "function",
+ "description": "Get a preference's value",
+ "parameters": [{
+ "name": "name",
+ "type": "string",
+ "description": "The preference name"
+ }],
+ "async": true
+ },
+ {
+ "name": "setPref",
+ "type": "function",
+ "description": "Set a preference's value",
+ "parameters": [
+ {
+ "name": "name",
+ "type": "string",
+ "description": "The preference name"
+ },
+ {
+ "name": "value",
+ "type": "boolean",
+ "description": "The new value"
+ }
+ ],
+ "async": true
+ }
+ ]
+ }
+]
deleted file mode 100644
--- a/browser/extensions/webcompat/bootstrap.js
+++ /dev/null
@@ -1,116 +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/. */
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-const PREF_BRANCH = "extensions.webcompat.";
-const PREF_DEFAULTS = {
- perform_injections: true,
- perform_ua_overrides: true,
-};
-
-const INJECTIONS_ENABLE_PREF_NAME = "extensions.webcompat.perform_injections";
-
-const BROWSER_STARTUP_FINISHED_TOPIC = "browser-delayed-startup-finished";
-
-const UA_OVERRIDES_INIT_TOPIC = "useragentoverrides-initialized";
-const UA_ENABLE_PREF_NAME = "extensions.webcompat.perform_ua_overrides";
-
-ChromeUtils.defineModuleGetter(this, "UAOverrider", "chrome://webcompat/content/lib/ua_overrider.jsm");
-ChromeUtils.defineModuleGetter(this, "UAOverrides", "chrome://webcompat/content/data/ua_overrides.jsm");
-
-let overrider;
-let webextensionPort;
-
-function InjectionsEnablePrefObserver() {
- let isEnabled = Services.prefs.getBoolPref(INJECTIONS_ENABLE_PREF_NAME);
- webextensionPort.postMessage({
- type: "injection-pref-changed",
- prefState: isEnabled,
- });
-}
-
-function UAEnablePrefObserver() {
- let isEnabled = Services.prefs.getBoolPref(UA_ENABLE_PREF_NAME);
- overrider.setShouldOverride(isEnabled);
-}
-
-function setDefaultPrefs() {
- const branch = Services.prefs.getDefaultBranch(PREF_BRANCH);
- for (const [key, val] of Object.entries(PREF_DEFAULTS)) {
- // If someone beat us to setting a default, don't overwrite it.
- if (branch.getPrefType(key) !== branch.PREF_INVALID) {
- continue;
- }
-
- switch (typeof val) {
- case "boolean":
- branch.setBoolPref(key, val);
- break;
- case "number":
- branch.setIntPref(key, val);
- break;
- case "string":
- branch.setCharPref(key, val);
- break;
- }
- }
-}
-
-this.install = function() {};
-this.uninstall = function() {};
-
-this.startup = function({webExtension}) {
- setDefaultPrefs();
-
- // Intentionally reset the preference on every browser restart to avoid site
- // breakage by accidentally toggled preferences or by leaving it off after
- // debugging a site.
- Services.prefs.clearUserPref(INJECTIONS_ENABLE_PREF_NAME);
- Services.prefs.addObserver(INJECTIONS_ENABLE_PREF_NAME, InjectionsEnablePrefObserver);
-
- Services.prefs.clearUserPref(UA_ENABLE_PREF_NAME);
- Services.prefs.addObserver(UA_ENABLE_PREF_NAME, UAEnablePrefObserver);
-
- // Listen to the useragentoverrides-initialized notification we get and
- // initialize our overrider there. This is done to avoid slowing down the
- // apparent startup process, since we avoid loading anything before the first
- // window is visible to the user. See bug 1371442 for details.
- let uaStartupObserver = {
- observe(aSubject, aTopic, aData) {
- if (aTopic !== UA_OVERRIDES_INIT_TOPIC) {
- return;
- }
-
- Services.obs.removeObserver(this, UA_OVERRIDES_INIT_TOPIC);
- overrider = new UAOverrider(UAOverrides);
- overrider.init();
- },
- };
- Services.obs.addObserver(uaStartupObserver, UA_OVERRIDES_INIT_TOPIC);
-
- // Observe browser-delayed-startup-finished and only initialize our embedded
- // WebExtension after that. Otherwise, we'd try to initialize as soon as the
- // browser starts up, which adds a heavy startup penalty.
- let appStartupObserver = {
- observe(aSubject, aTopic, aData) {
- webExtension.startup().then((api) => {
- api.browser.runtime.onConnect.addListener((port) => {
- webextensionPort = port;
- });
-
- return Promise.resolve();
- }).catch((ex) => {
- console.error(ex);
- });
- Services.obs.removeObserver(this, BROWSER_STARTUP_FINISHED_TOPIC);
- },
- };
- Services.obs.addObserver(appStartupObserver, BROWSER_STARTUP_FINISHED_TOPIC);
-};
-
-this.shutdown = function() {
- Services.prefs.removeObserver(INJECTIONS_ENABLE_PREF_NAME, InjectionsEnablePrefObserver);
- Services.prefs.removeObserver(UA_ENABLE_PREF_NAME, UAEnablePrefObserver);
-};
deleted file mode 100644
--- a/browser/extensions/webcompat/content/data/ua_overrides.jsm
+++ /dev/null
@@ -1,65 +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/. */
-
-/**
- * For detailed information on our policies, and a documention on this format
- * and its possibilites, please check the Mozilla-Wiki at
- *
- * https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
- */
-const UAOverrides = [
-
- /*
- * This is a dummy override that applies a Chrome UA to a dummy site that
- * blocks all browsers but Chrome.
- *
- * This was only put in place to allow QA to test this system addon on an
- * actual site, since we were not able to find a proper override in time.
- */
- {
- baseDomain: "schub.io",
- applications: ["firefox", "fennec"],
- uriMatcher: (uri) => uri.includes("webcompat-addon-testcases.schub.io"),
- uaTransformer: (originalUA) => {
- let prefix = originalUA.substr(0, originalUA.indexOf(")") + 1);
- return `${prefix} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36`;
- },
- },
-
- /*
- * Bug 1480710 - m.imgur.com - Build UA override
- * WebCompat issue #13154 - https://webcompat.com/issues/13154
- *
- * imgur returns a 404 for requests to CSS and JS file if requested with a Fennec
- * User Agent. By removing the Fennec identifies and adding Chrome Mobile's, we
- * receive the correct CSS and JS files.
- */
- {
- baseDomain: "imgur.com",
- applications: ["fennec"],
- uriMatcher: (uri) => uri.includes("m.imgur.com"),
- uaTransformer: (originalUA) => {
- let prefix = originalUA.substr(0, originalUA.indexOf(")") + 1);
- return prefix + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36";
- },
- },
-
- /*
- * Bug 755590 - sites.google.com - top bar doesn't show up in Firefox for Android
- *
- * Google Sites does show a different top bar template based on the User Agent.
- * For Fennec, this results in a broken top bar. Appending Chrome and Mobile Safari
- * identifiers to the UA results in a correct rendering.
- */
- {
- baseDomain: "google.com",
- applications: ["fennec"],
- uriMatcher: (uri) => uri.includes("sites.google.com"),
- uaTransformer: (originalUA) => {
- return originalUA + " Chrome/68.0.3440.85 Mobile Safari/537.366";
- },
- },
-];
-
-var EXPORTED_SYMBOLS = ["UAOverrides"]; /* exported UAOverrides */
deleted file mode 100644
--- a/browser/extensions/webcompat/content/lib/ua_overrider.jsm
+++ /dev/null
@@ -1,126 +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/. */
-
-ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
-ChromeUtils.defineModuleGetter(this, "UserAgentOverrides", "resource://gre/modules/UserAgentOverrides.jsm");
-
-class UAOverrider {
- constructor(overrides) {
- this._overrides = {};
- this._shouldOverride = true;
-
- this.initOverrides(overrides);
- }
-
- initOverrides(overrides) {
- // on xpcshell tests, there is no implementation for nsIXULAppInfo, so this
- // might fail there. To have all of our test cases running at all times,
- // assume they are on Desktop for now.
- let currentApplication = "firefox";
- try {
- currentApplication = Services.appinfo.name.toLowerCase();
- } catch (ex) {
- console.warn("Getting appinfo.name failed, assuming we run on Desktop.", ex);
- }
-
- for (let override of overrides) {
- // Firefox for Desktop is the default application for all overrides.
- if (!override.applications) {
- override.applications = ["firefox"];
- }
-
- // If the current application is not targeted by the override in question,
- // we can skip adding the override to our checks entirely.
- if (!override.applications.includes(currentApplication)) {
- continue;
- }
-
- if (!this._overrides[override.baseDomain]) {
- this._overrides[override.baseDomain] = [];
- }
-
- if (!override.uriMatcher) {
- override.uriMatcher = () => true;
- }
-
- this._overrides[override.baseDomain].push(override);
- }
- }
-
- /**
- * Used for disabling overrides when the pref has been flipped to false.
- *
- * Since we no longer use our own event handlers, we check this bool in our
- * override callback and simply return early if we are not supposed to do
- * anything.
- */
- setShouldOverride(newState) {
- this._shouldOverride = newState;
- }
-
- init() {
- UserAgentOverrides.addComplexOverride(this.overrideCallback.bind(this));
- }
-
- overrideCallback(channel, defaultUA) {
- if (!this._shouldOverride) {
- return false;
- }
-
- let uaOverride = this.lookupUAOverride(channel.URI, defaultUA);
- if (uaOverride) {
- console.log("The user agent has been overridden for compatibility reasons.");
- return uaOverride;
- }
-
- return false;
- }
-
- /**
- * Try to use the eTLDService to get the base domain (will return example.com
- * for http://foo.bar.example.com/foo/bar).
- *
- * However, the eTLDService is a bit picky and throws whenever we pass a
- * blank host name or an IP into it, see bug 1337785. Since we do not plan on
- * override UAs for such cases, we simply catch everything and return false.
- */
- getBaseDomainFromURI(uri) {
- try {
- return Services.eTLD.getBaseDomain(uri);
- } catch (_) {
- return false;
- }
- }
-
- /**
- * This function returns a User Agent based on the URI passed into. All
- * override rules are defined in data/ua_overrides.jsm and the required format
- * is explained there.
- *
- * Since it is expected and designed to have more than one override per base
- * domain, we have to loop over this._overrides[baseDomain], which contains
- * all available overrides.
- *
- * If the uriMatcher function returns true, the uaTransformer function gets
- * called and its result will be used as the Use Agent for the current
- * request.
- *
- * If there are more than one possible overrides, that is if two or more
- * uriMatchers would return true, the first one gets applied.
- */
- lookupUAOverride(uri, defaultUA) {
- let baseDomain = this.getBaseDomainFromURI(uri);
- if (baseDomain && this._overrides[baseDomain]) {
- for (let uaOverride of this._overrides[baseDomain]) {
- if (uaOverride.uriMatcher(uri.specIgnoringRef)) {
- return uaOverride.uaTransformer(defaultUA);
- }
- }
- }
-
- return false;
- }
-}
-
-var EXPORTED_SYMBOLS = ["UAOverrider"]; /* exported UAOverrider */
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat/injections.js
@@ -0,0 +1,102 @@
+/**
+ * For detailed information on our policies, and a documention on this format
+ * and its possibilites, please check the Mozilla-Wiki at
+ *
+ * https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
+ */
+const contentScripts = {
+ universal: [
+ {
+ matches: ["*://webcompat-addon-testcases.schub.io/*"],
+ css: [{file: "injections/css/bug0000000-dummy-css-injection.css"}],
+ js: [{file: "injections/js/bug0000000-dummy-js-injection.js"}],
+ runAt: "document_start",
+ },
+ ],
+ desktop: [
+ {
+ matches: ["https://ib.absa.co.za/*"],
+ js: [{file: "injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js"}],
+ runAt: "document_start",
+ },
+ {
+ matches: ["http://histography.io/*"],
+ js: [{file: "injections/js/bug1457335-histography.io-ua-change.js"}],
+ runAt: "document_start",
+ },
+ {
+ matches: ["*://*.bankofamerica.com/*"],
+ js: [{file: "injections/js/bug1472075-bankofamerica.com-ua-change.js"}],
+ runAt: "document_start",
+ },
+ {
+ matches: ["http://202.166.205.141/bbvrs/*"],
+ js: [{file: "injections/js/bug1472081-election.gov.np-window.sidebar-shim.js"}],
+ runAt: "document_start",
+ allFrames: true,
+ },
+ {
+ matches: ["*://portalminasnet.com/*"],
+ js: [{file: "injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js"}],
+ runAt: "document_start",
+ allFrames: true,
+ },
+ ],
+ android: [],
+};
+
+/* globals browser */
+
+let port = browser.runtime.connect();
+let registeredContentScripts = [];
+
+async function registerContentScripts() {
+ let platform = "desktop";
+ let platformInfo = await browser.runtime.getPlatformInfo();
+ if (platformInfo.os == "android") {
+ platform = "android";
+ }
+
+ let targetContentScripts = contentScripts.universal.concat(contentScripts[platform]);
+ targetContentScripts.forEach(async (contentScript) => {
+ try {
+ let handle = await browser.contentScripts.register(contentScript);
+ registeredContentScripts.push(handle);
+ } catch (ex) {
+ console.error("Registering WebCompat GoFaster content scripts failed: ", ex);
+ }
+ });
+}
+
+function unregisterContentScripts() {
+ registeredContentScripts.forEach((contentScript) => {
+ contentScript.unregister();
+ });
+}
+
+port.onMessage.addListener((message) => {
+ switch (message.type) {
+ case "injection-pref-changed":
+ if (message.prefState) {
+ registerContentScripts();
+ } else {
+ unregisterContentScripts();
+ }
+ break;
+ }
+});
+
+const INJECTION_PREF = "perform_injections";
+function checkInjectionPref() {
+ browser.aboutConfigPrefs.getPref(INJECTION_PREF).then(value => {
+ if (value === undefined) {
+ browser.aboutConfigPrefs.setPref(INJECTION_PREF, true);
+ } else if (value === false) {
+ unregisterContentScripts();
+ } else {
+ registerContentScripts();
+ }
+ });
+}
+browser.aboutConfigPrefs.onPrefChange.addListener(checkInjectionPref, INJECTION_PREF);
+checkInjectionPref();
rename from browser/extensions/webcompat/webextension/injections/css/bug0000000-dummy-css-injection.css
rename to browser/extensions/webcompat/injections/css/bug0000000-dummy-css-injection.css
rename from browser/extensions/webcompat/webextension/injections/js/bug0000000-dummy-js-injection.js
rename to browser/extensions/webcompat/injections/js/bug0000000-dummy-js-injection.js
rename from browser/extensions/webcompat/webextension/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js
rename to browser/extensions/webcompat/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js
rename from browser/extensions/webcompat/webextension/injections/js/bug1457335-histography.io-ua-change.js
rename to browser/extensions/webcompat/injections/js/bug1457335-histography.io-ua-change.js
rename from browser/extensions/webcompat/webextension/injections/js/bug1472075-bankofamerica.com-ua-change.js
rename to browser/extensions/webcompat/injections/js/bug1472075-bankofamerica.com-ua-change.js
rename from browser/extensions/webcompat/webextension/injections/js/bug1472081-election.gov.np-window.sidebar-shim.js
rename to browser/extensions/webcompat/injections/js/bug1472081-election.gov.np-window.sidebar-shim.js
rename from browser/extensions/webcompat/webextension/injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js
rename to browser/extensions/webcompat/injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js
deleted file mode 100644
--- a/browser/extensions/webcompat/install.rdf.in
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.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/. -->
-
-#filter substitution
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
- <Description about="urn:mozilla:install-manifest">
- <em:id>webcompat@mozilla.org</em:id>
- <em:version>2.0.1</em:version>
- <em:type>2</em:type>
- <em:bootstrap>true</em:bootstrap>
- <em:multiprocessCompatible>true</em:multiprocessCompatible>
- <em:hasEmbeddedWebExtension>true</em:hasEmbeddedWebExtension>
-
- <!-- Firefox Desktop -->
- <em:targetApplication>
- <Description>
- <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
- <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
- <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
- </Description>
- </em:targetApplication>
-
- <!-- Firefox for Android -->
- <em:targetApplication>
- <Description>
- <em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id>
- <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
- <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
- </Description>
- </em:targetApplication>
-
- <!-- Front End MetaData -->
- <em:name>Web Compat</em:name>
- <em:description>Urgent post-release fixes for web compatibility.</em:description>
- </Description>
-</RDF>
deleted file mode 100644
--- a/browser/extensions/webcompat/jar.mn
+++ /dev/null
@@ -1,3 +0,0 @@
-[features/webcompat@mozilla.org] chrome.jar:
-% content webcompat %content/
- content/ (content/*)
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat/manifest.json
@@ -0,0 +1,37 @@
+{
+ "manifest_version": 2,
+ "name": "Web Compat",
+ "description": "Urgent post-release fixes for web compatibility.",
+ "version": "3.0.0",
+
+ "applications": {
+ "gecko": {
+ "id": "webcompat@mozilla.org",
+ "strict_min_version": "59.0b5"
+ }
+ },
+
+ "experiment_apis": {
+ "aboutConfigPrefs": {
+ "schema": "aboutConfigPrefs.json",
+ "parent": {
+ "scopes": ["addon_parent"],
+ "script": "aboutConfigPrefs.js",
+ "paths": [["aboutConfigPrefs"]]
+ }
+ }
+ },
+
+ "permissions": [
+ "webRequest",
+ "webRequestBlocking",
+ "<all_urls>"
+ ],
+
+ "background": {
+ "scripts": [
+ "injections.js",
+ "ua_overrides.js"
+ ]
+ }
+}
--- a/browser/extensions/webcompat/moz.build
+++ b/browser/extensions/webcompat/moz.build
@@ -3,38 +3,30 @@
# 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/.
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
FINAL_TARGET_FILES.features['webcompat@mozilla.org'] += [
- 'bootstrap.js'
-]
-
-FINAL_TARGET_FILES.features['webcompat@mozilla.org']['webextension'] += [
- 'webextension/background.js',
- 'webextension/manifest.json'
-]
-
-FINAL_TARGET_FILES.features['webcompat@mozilla.org']['webextension']['injections']['css'] += [
- 'webextension/injections/css/bug0000000-dummy-css-injection.css'
+ 'aboutConfigPrefs.js',
+ 'aboutConfigPrefs.json',
+ 'injections.js',
+ 'manifest.json',
+ 'ua_overrides.js'
]
-FINAL_TARGET_FILES.features['webcompat@mozilla.org']['webextension']['injections']['js'] += [
- 'webextension/injections/js/bug0000000-dummy-js-injection.js',
- 'webextension/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js',
- 'webextension/injections/js/bug1457335-histography.io-ua-change.js',
- 'webextension/injections/js/bug1472075-bankofamerica.com-ua-change.js',
- 'webextension/injections/js/bug1472081-election.gov.np-window.sidebar-shim.js',
- 'webextension/injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js'
+FINAL_TARGET_FILES.features['webcompat@mozilla.org']['injections']['css'] += [
+ 'injections/css/bug0000000-dummy-css-injection.css'
]
-FINAL_TARGET_PP_FILES.features['webcompat@mozilla.org'] += [
- 'install.rdf.in'
+FINAL_TARGET_FILES.features['webcompat@mozilla.org']['injections']['js'] += [
+ 'injections/js/bug0000000-dummy-js-injection.js',
+ 'injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js',
+ 'injections/js/bug1457335-histography.io-ua-change.js',
+ 'injections/js/bug1472075-bankofamerica.com-ua-change.js',
+ 'injections/js/bug1472081-election.gov.np-window.sidebar-shim.js',
+ 'injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js'
]
-BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
-JAR_MANIFESTS += ['jar.mn']
-
with Files('**'):
BUG_COMPONENT = ('Web Compatibility Tools', 'Go Faster')
deleted file mode 100644
--- a/browser/extensions/webcompat/test/.eslintrc.js
+++ /dev/null
@@ -1,7 +0,0 @@
-"use strict";
-
-module.exports = {
- "extends": [
- "plugin:mozilla/browser-test"
- ]
-};
deleted file mode 100644
--- a/browser/extensions/webcompat/test/browser.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[DEFAULT]
-
-[browser_check_installed.js]
-[browser_overrider.js]
deleted file mode 100644
--- a/browser/extensions/webcompat/test/browser_check_installed.js
+++ /dev/null
@@ -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/. */
-
-"use strict";
-
-add_task(async function installed() {
- let addon = await AddonManager.getAddonByID("webcompat@mozilla.org");
- isnot(addon, null, "Webcompat addon should exist");
- is(addon.name, "Web Compat");
- ok(addon.isCompatible, "Webcompat addon is compatible with Firefox");
- ok(!addon.appDisabled, "Webcompat addon is not app disabled");
- ok(addon.isActive, "Webcompat addon is active");
- is(addon.type, "extension", "Webcompat addon is type extension");
-});
deleted file mode 100644
--- a/browser/extensions/webcompat/test/browser_overrider.js
+++ /dev/null
@@ -1,38 +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";
-
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "UAOverrider", "chrome://webcompat/content/lib/ua_overrider.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "IOService", "@mozilla.org/network/io-service;1", "nsIIOService");
-
-function getnsIURI(uri) {
- return IOService.newURI(uri, "utf-8");
-}
-
-add_task(function test() {
- let overrider = new UAOverrider([
- {
- baseDomain: "example.org",
- uaTransformer: () => "Test UA",
- },
- ]);
-
- let finalUA = overrider.lookupUAOverride(getnsIURI("http://www.example.org/foobar/"));
- is(finalUA, "Test UA", "Overrides the UA without a matcher function");
-});
-
-add_task(function test() {
- let overrider = new UAOverrider([
- {
- baseDomain: "example.org",
- uriMatcher: () => false,
- uaTransformer: () => "Test UA",
- },
- ]);
-
- let finalUA = overrider.lookupUAOverride(getnsIURI("http://www.example.org/foobar/"));
- isnot(finalUA, "Test UA", "Does not override the UA with the matcher returning false");
-});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat/ua_overrides.js
@@ -0,0 +1,129 @@
+/**
+ * For detailed information on our policies, and a documention on this format
+ * and its possibilites, please check the Mozilla-Wiki at
+ *
+ * https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
+ */
+const UAOverrides = {
+ universal: [
+ /*
+ * This is a dummy override that applies a Chrome UA to a dummy site that
+ * blocks all browsers but Chrome.
+ *
+ * This was only put in place to allow QA to test this system addon on an
+ * actual site, since we were not able to find a proper override in time.
+ */
+ {
+ matches: ["*://webcompat-addon-testcases.schub.io/*"],
+ uaTransformer: (originalUA) => {
+ let prefix = originalUA.substr(0, originalUA.indexOf(")") + 1);
+ return `${prefix} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36`;
+ },
+ },
+ ],
+ desktop: [],
+ android: [
+ /*
+ * Bug 1480710 - m.imgur.com - Build UA override
+ * WebCompat issue #13154 - https://webcompat.com/issues/13154
+ *
+ * imgur returns a 404 for requests to CSS and JS file if requested with a Fennec
+ * User Agent. By removing the Fennec identifies and adding Chrome Mobile's, we
+ * receive the correct CSS and JS files.
+ */
+ {
+ matches: ["*://m.imgur.com/*"],
+ uaTransformer: (originalUA) => {
+ let prefix = originalUA.substr(0, originalUA.indexOf(")") + 1);
+ return prefix + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36";
+ },
+ },
+
+ /*
+ * Bug 755590 - sites.google.com - top bar doesn't show up in Firefox for Android
+ *
+ * Google Sites does show a different top bar template based on the User Agent.
+ * For Fennec, this results in a broken top bar. Appending Chrome and Mobile Safari
+ * identifiers to the UA results in a correct rendering.
+ */
+ {
+ matches: ["*://sites.google.com/*"],
+ uaTransformer: (originalUA) => {
+ return originalUA + " Chrome/68.0.3440.85 Mobile Safari/537.366";
+ },
+ },
+
+ /*
+ * Bug 945963 - tieba.baidu.com serves simplified mobile content to Firefox Android
+ * WebCompat issue #18455 - https://webcompat.com/issues/18455
+ *
+ * tieba.baidu.com and tiebac.baidu.com serve a heavily simplified and less functional
+ * mobile experience to Firefox for Android users. Adding the AppleWebKit indicator
+ * to the User Agent gets us the same experience.
+ */
+ {
+ matches: ["*://tieba.baidu.com/*", "*://tiebac.baidu.com/*"],
+ uaTransformer: (originalUA) => {
+ return originalUA + " AppleWebKit/537.36 (KHTML, like Gecko)";
+ },
+ },
+ ],
+};
+
+/* globals browser */
+
+let activeListeners = [];
+function buildAndRegisterListener(matches, transformer) {
+ let listener = (details) => {
+ for (var header of details.requestHeaders) {
+ if (header.name.toLowerCase() === "user-agent") {
+ header.value = transformer(header.value);
+ }
+ }
+ return {requestHeaders: details.requestHeaders};
+ };
+
+ browser.webRequest.onBeforeSendHeaders.addListener(
+ listener,
+ {urls: matches},
+ ["blocking", "requestHeaders"]
+ );
+
+ activeListeners.push(listener);
+}
+
+async function registerUAOverrides() {
+ let platform = "desktop";
+ let platformInfo = await browser.runtime.getPlatformInfo();
+ if (platformInfo.os == "android") {
+ platform = "android";
+ }
+
+ let targetOverrides = UAOverrides.universal.concat(UAOverrides[platform]);
+ targetOverrides.forEach((override) => {
+ buildAndRegisterListener(override.matches, override.uaTransformer);
+ });
+}
+
+function unregisterUAOverrides() {
+ activeListeners.forEach((listener) => {
+ browser.webRequest.onBeforeSendHeaders.removeListener(listener);
+ });
+
+ activeListeners = [];
+}
+
+const OVERRIDE_PREF = "perform_ua_overrides";
+function checkOverridePref() {
+ browser.aboutConfigPrefs.getPref(OVERRIDE_PREF).then(value => {
+ if (value === undefined) {
+ browser.aboutConfigPrefs.setPref(OVERRIDE_PREF, true);
+ } else if (value === false) {
+ unregisterUAOverrides();
+ } else {
+ registerUAOverrides();
+ }
+ });
+}
+browser.aboutConfigPrefs.onPrefChange.addListener(checkOverridePref, OVERRIDE_PREF);
+checkOverridePref();
deleted file mode 100644
--- a/browser/extensions/webcompat/webextension/background.js
+++ /dev/null
@@ -1,94 +0,0 @@
-/**
- * For detailed information on our policies, and a documention on this format
- * and its possibilites, please check the Mozilla-Wiki at
- *
- * https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
- */
-const contentScripts = {
- universal: [
- {
- matches: ["*://webcompat-addon-testcases.schub.io/*"],
- css: [{file: "injections/css/bug0000000-dummy-css-injection.css"}],
- js: [{file: "injections/js/bug0000000-dummy-js-injection.js"}],
- runAt: "document_start",
- },
- ],
- desktop: [
- {
- matches: ["https://ib.absa.co.za/*"],
- js: [{file: "injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js"}],
- runAt: "document_start",
- },
- {
- matches: ["http://histography.io/*"],
- js: [{file: "injections/js/bug1457335-histography.io-ua-change.js"}],
- runAt: "document_start",
- },
- {
- matches: ["*://*.bankofamerica.com/*"],
- js: [{file: "injections/js/bug1472075-bankofamerica.com-ua-change.js"}],
- runAt: "document_start",
- },
- {
- matches: ["http://202.166.205.141/bbvrs/*"],
- js: [{file: "injections/js/bug1472081-election.gov.np-window.sidebar-shim.js"}],
- runAt: "document_start",
- allFrames: true,
- },
- {
- matches: ["*://portalminasnet.com/*"],
- js: [{file: "injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js"}],
- runAt: "document_start",
- allFrames: true,
- },
- ],
- android: [],
-};
-
-/* globals browser */
-
-let port = browser.runtime.connect();
-let registeredContentScripts = [];
-
-async function registerContentScripts() {
- let platform = "desktop";
- let platformInfo = await browser.runtime.getPlatformInfo();
- if (platformInfo.os == "android") {
- platform = "android";
- }
-
- let targetContentScripts = contentScripts.universal.concat(contentScripts[platform]);
- targetContentScripts.forEach(async (contentScript) => {
- try {
- let handle = await browser.contentScripts.register(contentScript);
- registeredContentScripts.push(handle);
- } catch (ex) {
- console.error("Registering WebCompat GoFaster content scripts failed: ", ex);
- }
- });
-}
-
-function unregisterContentScripts() {
- registeredContentScripts.forEach((contentScript) => {
- contentScript.unregister();
- });
-}
-
-port.onMessage.addListener((message) => {
- switch (message.type) {
- case "injection-pref-changed":
- if (message.prefState) {
- registerContentScripts();
- } else {
- unregisterContentScripts();
- }
- break;
- }
-});
-
-/**
- * Note that we reset all preferences on extension startup, so the injections will
- * never be disabled when this loads up. Because of that, we can simply register
- * right away.
- */
-registerContentScripts();
deleted file mode 100644
--- a/browser/extensions/webcompat/webextension/manifest.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "manifest_version": 2,
- "name": "Web Compat",
- "description": "Urgent post-release fixes for web compatibility.",
- "version": "2.0",
-
- "applications": {
- "gecko": {
- "id": "webcompat@mozilla.org",
- "strict_min_version": "59.0b5"
- }
- },
-
- "permissions": [
- "<all_urls>"
- ],
-
- "background": {
- "scripts": [
- "background.js"
- ]
- }
-}
--- a/browser/locales/en-US/browser/preferences/preferences.ftl
+++ b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -92,16 +92,20 @@ restart-later = Restart Later
# This string is shown to notify the user that their home page
# is being controlled by an extension.
extension-controlled-homepage-override = An extension, <img data-l10n-name="icon"/> { $name }, is controlling your home page.
# This string is shown to notify the user that their new tab page
# is being controlled by an extension.
extension-controlled-new-tab-url = An extension, <img data-l10n-name="icon"/> { $name }, is controlling your New Tab page.
+# This string is shown to notify the user that their notifications permission
+# is being controlled by an extension.
+extension-controlled-web-notifications= An extension, <img data-l10n-name="icon"/> { $name }, is controlling this setting.
+
# This string is shown to notify the user that the default search engine
# is being controlled by an extension.
extension-controlled-default-search = An extension, <img data-l10n-name="icon"/> { $name }, has set your default search engine.
# This string is shown to notify the user that Container Tabs
# are being enabled by an extension.
extension-controlled-privacy-containers = An extension, <img data-l10n-name="icon"/> { $name }, requires Container Tabs.
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -116,16 +116,18 @@ when there are no windows but Firefox is
<!ENTITY printSetupCmd.label "Page Setup…">
<!ENTITY printSetupCmd.accesskey "u">
<!ENTITY printPreviewCmd.label "Print Preview">
<!ENTITY printPreviewCmd.accesskey "v">
<!ENTITY printCmd.label "Print…">
<!ENTITY printCmd.accesskey "P">
<!ENTITY printCmd.commandkey "p">
+<!ENTITY taskManagerCmd.label "Task Manager">
+
<!ENTITY goOfflineCmd.label "Work Offline">
<!ENTITY goOfflineCmd.accesskey "k">
<!ENTITY menubarCmd.label "Menu Bar">
<!ENTITY menubarCmd.accesskey "M">
<!ENTITY navbarCmd.label "Navigation Toolbar">
<!ENTITY personalbarCmd.label "Bookmarks Toolbar">
<!ENTITY personalbarCmd.accesskey "B">
@@ -995,16 +997,17 @@ you can use these alternative items. Oth
<!ENTITY contentBlocking.trackingProtection.blocking.label "Blocking">
<!-- LOCALIZATION NOTE (contentBlocking.trackingProtection.add.label):
This is displayed as a link to preferences, where the user can add
this specific type of content blocking. When this text is shown
the type of content blocking is currently not enabled. -->
<!ENTITY contentBlocking.trackingProtection.add.label "Add Blocking…">
<!ENTITY contentBlocking.3rdPartyCookies.label "Third-Party Cookies">
+<!ENTITY contentBlocking.3rdPartyCookies.trackers.label "Tracking Cookies">
<!-- LOCALIZATION NOTE (contentBlocking.3rdPartyCookies.blocked.label):
This label signals that this type of content blocking is turned
ON and is successfully blocking third-party cookies, so this is
a positive thing. It forms the end of the (imaginary) sentence
"Third-Party Cookies [are] Blocked"-->
<!ENTITY contentBlocking.3rdPartyCookies.blocked.label "Blocked">
<!-- LOCALIZATION NOTE (contentBlocking.tranckingProtection.blocking.label):
This label signals that this type of content blocking is turned
--- a/browser/modules/Sanitizer.jsm
+++ b/browser/modules/Sanitizer.jsm
@@ -706,24 +706,16 @@ async function sanitizeOnShutdown(progre
}
async function sanitizeSessionPrincipals() {
if (Services.prefs.getIntPref(PREF_COOKIE_LIFETIME,
Ci.nsICookieService.ACCEPT_NORMALLY) != Ci.nsICookieService.ACCEPT_SESSION) {
return;
}
- // When PREF_COOKIE_LIFETIME is set to ACCEPT_SESSION, any new cookie will be
- // marked as session only. But we don't touch the existing ones. For this
- // reason, here we delete any existing cookie, at shutdown.
- await new Promise(resolve => {
- Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_COOKIES,
- resolve);
- });
-
let principals = await new Promise(resolve => {
quotaManagerService.getUsage(request => {
if (request.resultCode != Cr.NS_OK) {
// We are probably shutting down. We don't want to propagate the
// error, rejecting the promise.
resolve([]);
return;
}
--- a/browser/themes/osx/downloads/allDownloadsView.css
+++ b/browser/themes/osx/downloads/allDownloadsView.css
@@ -5,15 +5,15 @@
%include ../../shared/downloads/allDownloadsView.inc.css
/*** List items ***/
:root {
--downloads-item-height: 6em;
}
-.downloadProgress > .progress-bar {
+.downloadProgress::-moz-progress-bar {
background-color: #3c9af8;
}
-.downloadProgress[paused="true"] > .progress-bar {
+.downloadProgress[paused]::-moz-progress-bar {
background-color: #a6a6a6;
}
--- a/browser/themes/osx/downloads/downloads.css
+++ b/browser/themes/osx/downloads/downloads.css
@@ -28,21 +28,21 @@
@item@[verdict="Malware"]:not(:hover) {
color: #aa1b08;
}
:root[lwt-popup-brighttext] @item@[verdict="Malware"]:not(:hover) {
color: #ff0039;
}
-.downloadProgress > .progress-bar {
+.downloadProgress::-moz-progress-bar {
background-color: #3c9af8;
}
-.downloadProgress[paused="true"] > .progress-bar {
+.downloadProgress[paused]::-moz-progress-bar {
background-color: #a6a6a6;
}
/*** Highlighted list items ***/
@keyfocus@ @itemFocused@ {
outline: 2px -moz-mac-focusring solid;
outline-offset: -2px;
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -167,16 +167,20 @@
margin-left: 0;
margin-right: 0;
}
#widget-overflow .webextension-popup-browser {
background: #fff;
}
+#addon-progress-notification-progressmeter {
+ margin: 2px 4px;
+}
+
/* Contextual Feature Recommendation popup-notification */
:root {
--cfr-notification-header-image: url(resource://activity-stream/data/content/assets/glyph-help-24.svg);
--cfr-notification-footer-star: url(resource://activity-stream/data/content/assets/glyph-star-17.svg);
}
#cfr-notification-header {
--- a/browser/themes/shared/downloads/progressmeter.inc.css
+++ b/browser/themes/shared/downloads/progressmeter.inc.css
@@ -1,65 +1,58 @@
/*** Common-styled progressmeter ***/
+
+/*
+ * Styling "html:progress" is limited by the fact that a number of properties
+ * are intentionally locked at the UA stylesheet level. We have to use a border
+ * instead of an outline because the latter would be drawn over the progress
+ * bar and we cannot change its z-index. This means we have to use a negative
+ * margin, except when the value is zero, and adjust the width calculation for
+ * the indeterminate state.
+ */
+
.downloadProgress {
- height: 8px;
- border-radius: 1px;
+ -moz-appearance: none;
+ display: -moz-box;
margin: 4px 0 0;
margin-inline-end: 12px;
-
- /* for overriding rules in progressmeter.css */
- -moz-appearance: none;
- border-style: none;
- background-color: transparent;
- min-width: initial;
- min-height: initial;
+ border: 1px solid ButtonShadow;
+ height: 6px;
+ background-color: ButtonFace;
}
-.downloadProgress > .progress-bar {
+.downloadProgress::-moz-progress-bar {
+ -moz-appearance: none;
background-color: Highlight;
-
- /* for overriding rules in progressmeter.css */
- -moz-appearance: none;
}
-.downloadProgress[paused="true"] > .progress-bar {
+.downloadProgress[paused]::-moz-progress-bar {
background-color: GrayText;
}
-.downloadProgress[progress-undetermined] > .progress-bar {
+.downloadProgress:not([value="0"])::-moz-progress-bar {
+ margin: -1px;
+ height: 8px;
+}
+
+.downloadProgress:indeterminate::-moz-progress-bar {
+ width: calc(100% + 2px);
/* Make a white reflecting animation.
Create a gradient with 2 identical pattern, and enlarge the size to 200%.
This allows us to animate background-position with percentage. */
background-image: linear-gradient(90deg, transparent 0%,
rgba(255,255,255,0.5) 25%,
transparent 50%,
rgba(255,255,255,0.5) 75%,
transparent 100%);
background-blend-mode: lighten;
background-size: 200% 100%;
animation: downloadProgressSlideX 1.5s linear infinite;
}
-.downloadProgress > .progress-remainder {
- border: solid ButtonShadow;
- border-block-start-width: 1px;
- border-block-end-width: 1px;
- border-inline-start-width: 0;
- border-inline-end-width: 1px;
- background-color: ButtonFace;
-}
-
-.downloadProgress[value="0"] > .progress-remainder {
- border-width: 1px;
-}
-
-.downloadProgress[progress-undetermined] > .progress-remainder {
- border: none;
-}
-
@keyframes downloadProgressSlideX {
0% {
background-position: 0 0;
}
100% {
background-position: -100% 0;
}
}
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/icons/performance.svg
@@ -0,0 +1,7 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+ <path fill="context-fill" d="M8 1a8 8 0 0 0-8 8 7.89 7.89 0 0 0 .78 3.43 1 1 0 0 0 .9.57.94.94 0 0 0 .43-.1 1 1 0 0 0 .47-1.33A6.07 6.07 0 0 1 2 9a6 6 0 0 1 12 0 5.93 5.93 0 0 1-.59 2.57 1 1 0 0 0 1.81.86A7.89 7.89 0 0 0 16 9a8 8 0 0 0-8-8z"/>
+ <path fill="context-fill" d="M11.77 7.08a.5.5 0 0 0-.69.15L8.62 11.1A2.12 2.12 0 0 0 8 11a2 2 0 0 0 0 4 2.05 2.05 0 0 0 1.12-.34 2.31 2.31 0 0 0 .54-.54 2 2 0 0 0 0-2.24 2.31 2.31 0 0 0-.2-.24l2.46-3.87a.5.5 0 0 0-.15-.69z"/>
+</svg>
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -159,16 +159,17 @@
skin/classic/browser/link.svg (../shared/icons/link.svg)
skin/classic/browser/mail.svg (../shared/icons/mail.svg)
skin/classic/browser/menu.svg (../shared/icons/menu.svg)
skin/classic/browser/menu-badged.svg (../shared/icons/menu-badged.svg)
skin/classic/browser/new-tab.svg (../shared/icons/new-tab.svg)
skin/classic/browser/new-window.svg (../shared/icons/new-window.svg)
skin/classic/browser/open.svg (../shared/icons/open.svg)
skin/classic/browser/page-action.svg (../shared/icons/page-action.svg)
+ skin/classic/browser/performance.svg (../shared/icons/performance.svg)
skin/classic/browser/print.svg (../shared/icons/print.svg)
skin/classic/browser/private-browsing.svg (../shared/icons/private-browsing.svg)
skin/classic/browser/privateBrowsing.svg (../shared/icons/privateBrowsing.svg)
skin/classic/browser/restore-session.svg (../shared/icons/restore-session.svg)
skin/classic/browser/quit.svg (../shared/icons/quit.svg)
skin/classic/browser/reload.svg (../shared/icons/reload.svg)
skin/classic/browser/reload-to-stop.svg (../shared/icons/reload-to-stop.svg)
skin/classic/browser/save.svg (../shared/icons/save.svg)
--- a/browser/themes/shared/menupanel.inc.css
+++ b/browser/themes/shared/menupanel.inc.css
@@ -28,16 +28,20 @@
list-style-image: url(chrome://browser/skin/customize.svg);
}
#appMenu-find-button,
#panelMenu_searchBookmarks {
list-style-image: url(chrome://browser/skin/search-glass.svg);
}
+#appMenu-taskmanager-button {
+ list-style-image: url("chrome://browser/skin/performance.svg");
+}
+
#appMenu-help-button {
list-style-image: url(chrome://global/skin/icons/help.svg);
}
#appMenu-cut-button {
list-style-image: url(chrome://browser/skin/edit-cut.svg);
}
--- a/browser/themes/windows/downloads/allDownloadsView.css
+++ b/browser/themes/windows/downloads/allDownloadsView.css
@@ -6,19 +6,23 @@
/*** List items ***/
:root {
--downloads-item-height: 6em;
}
@media (-moz-windows-default-theme) {
- .downloadProgress > .progress-bar {
+ .downloadProgress::-moz-progress-bar {
background-color: #3c9af8;
}
+
+ .downloadProgress[paused]::-moz-progress-bar {
+ background-color: #a6a6a6;
+ }
}
/*** Highlighted list items ***/
@media (-moz-windows-default-theme) {
/*
-moz-appearance: menuitem is almost right, but the hover effect is not
transparent and is lighter than desired.
--- a/browser/themes/windows/downloads/downloads.css
+++ b/browser/themes/windows/downloads/downloads.css
@@ -32,24 +32,23 @@
color: #aa1b08;
}
:root[lwt-popup-brighttext] @item@[verdict="Malware"]:not(:hover) {
color: #ff0039;
}
/* Use unified color for the progressbar on default theme */
- .downloadProgress > .progress-bar {
+ .downloadProgress::-moz-progress-bar {
background-color: #3c9af8;
}
- .downloadProgress[paused="true"] > .progress-bar {
+ .downloadProgress[paused]::-moz-progress-bar {
background-color: #a6a6a6;
}
-
}
/*** Highlighted list items ***/
@keyfocus@ @itemFocused@ {
outline: 1px -moz-dialogtext dotted;
outline-offset: -1px;
}
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/ControlCenter.jsm
@@ -29,213 +29,210 @@ var ControlCenter = {
init(libDir) {
// Disable the FTU tours.
Services.prefs.setIntPref("privacy.trackingprotection.introCount", 20);
Services.prefs.setIntPref("browser.contentblocking.introCount", 20);
},
configurations: {
about: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage("about:rights");
await openIdentityPopup();
},
},
localFile: {
- selectors: ["#identity-popup"],
+ // This selector is different so we can exclude the changing file: path
+ selectors: ["#identity-popup-security"],
async applyConfig() {
let channel = NetUtil.newChannel({
uri: "resource://mozscreenshots/lib/mozscreenshots.html",
loadUsingSystemPrincipal: true,
});
channel = channel.QueryInterface(Ci.nsIFileChannel);
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let gBrowser = browserWindow.gBrowser;
BrowserTestUtils.loadURI(gBrowser.selectedBrowser, channel.file.path);
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
await openIdentityPopup();
},
-
- async verifyConfig() {
- return { todo: "Bug 1373563: intermittent controlCenter_localFile on Taskcluster" };
- },
},
http: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage(HTTP_PAGE);
await openIdentityPopup();
},
},
httpSubView: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage(HTTP_PAGE);
await openIdentityPopup(true);
},
},
https: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage(HTTPS_PAGE);
await openIdentityPopup();
},
},
httpsSubView: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage(HTTPS_PAGE);
await openIdentityPopup(true);
},
},
singlePermission: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
let uri = Services.io.newURI(PERMISSIONS_PAGE);
SitePermissions.set(uri, "camera", SitePermissions.ALLOW);
await loadPage(PERMISSIONS_PAGE);
await openIdentityPopup();
},
},
allPermissions: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
// TODO: (Bug 1330601) Rewrite this to consider temporary (TAB) permission states.
// There are 2 possible non-default permission states, so we alternate between them.
let states = [SitePermissions.ALLOW, SitePermissions.BLOCK];
let uri = Services.io.newURI(PERMISSIONS_PAGE);
SitePermissions.listPermissions().forEach(function(permission, index) {
SitePermissions.set(uri, permission, states[index % 2]);
});
await loadPage(PERMISSIONS_PAGE);
await openIdentityPopup();
},
},
mixed: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage(MIXED_CONTENT_URL);
await openIdentityPopup();
},
},
mixedSubView: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage(MIXED_CONTENT_URL);
await openIdentityPopup(true);
},
},
mixedPassive: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage(MIXED_PASSIVE_CONTENT_URL);
await openIdentityPopup();
},
},
mixedPassiveSubView: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage(MIXED_PASSIVE_CONTENT_URL);
await openIdentityPopup(true);
},
},
mixedActive: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage(MIXED_ACTIVE_CONTENT_URL);
await openIdentityPopup();
},
},
mixedActiveSubView: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage(MIXED_ACTIVE_CONTENT_URL);
await openIdentityPopup(true);
},
},
mixedActiveUnblocked: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let gBrowser = browserWindow.gBrowser;
await loadPage(MIXED_ACTIVE_CONTENT_URL);
gBrowser.ownerGlobal.gIdentityHandler.disableMixedContentProtection();
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, MIXED_ACTIVE_CONTENT_URL);
await openIdentityPopup();
},
},
mixedActiveUnblockedSubView: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let gBrowser = browserWindow.gBrowser;
await loadPage(MIXED_ACTIVE_CONTENT_URL);
gBrowser.ownerGlobal.gIdentityHandler.disableMixedContentProtection();
await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, MIXED_ACTIVE_CONTENT_URL);
await openIdentityPopup(true);
},
},
httpPassword: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage(HTTP_PASSWORD_PAGE);
await openIdentityPopup();
},
},
httpPasswordSubView: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
await loadPage(HTTP_PASSWORD_PAGE);
await openIdentityPopup(true);
},
},
trackingProtectionNoElements: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
await loadPage(HTTP_PAGE);
await openIdentityPopup();
},
},
trackingProtectionEnabled: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
await UrlClassifierTestUtils.addTestTrackers();
await loadPage(TRACKING_PAGE);
await openIdentityPopup();
},
},
trackingProtectionDisabled: {
- selectors: ["#identity-popup"],
+ selectors: ["#navigator-toolbox", "#identity-popup"],
async applyConfig() {
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let gBrowser = browserWindow.gBrowser;
Services.prefs.setBoolPref("privacy.trackingprotection.enabled", true);
await UrlClassifierTestUtils.addTestTrackers();
await loadPage(TRACKING_PAGE);
await openIdentityPopup();
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/LightweightThemes.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/LightweightThemes.jsm
@@ -20,68 +20,68 @@ var LightweightThemes = {
// convert -size 3000x200 canvas:#eee white_theme.png
let whiteImage = libDir.clone();
whiteImage.append("white_theme.png");
this._whiteImageURL = Services.io.newFileURI(whiteImage).spec;
},
configurations: {
noLWT: {
- selectors: ["#navigator-toolbox"],
+ selectors: [],
async applyConfig() {
LightweightThemeManager.currentTheme = null;
},
},
darkLWT: {
- selectors: ["#navigator-toolbox"],
+ selectors: [],
applyConfig() {
LightweightThemeManager.setLocalTheme({
id: "black",
name: "black",
headerURL: LightweightThemes._blackImageURL,
textcolor: "#eeeeee",
accentcolor: "#111111",
});
// Wait for LWT listener
return new Promise(resolve => {
setTimeout(() => {
- resolve("darkLWT");
+ resolve();
}, 500);
});
},
},
lightLWT: {
- selectors: ["#navigator-toolbox"],
+ selectors: [],
applyConfig() {
LightweightThemeManager.setLocalTheme({
id: "white",
name: "white",
headerURL: LightweightThemes._whiteImageURL,
textcolor: "#111111",
accentcolor: "#eeeeee",
});
// Wait for LWT listener
return new Promise(resolve => {
setTimeout(() => {
- resolve("lightLWT");
+ resolve();
}, 500);
});
},
},
compactLight: {
- selectors: ["#navigator-toolbox"],
+ selectors: [],
applyConfig() {
LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-compact-light@mozilla.org");
},
},
compactDark: {
- selectors: ["#navigator-toolbox"],
+ selectors: [],
applyConfig() {
LightweightThemeManager.currentTheme = LightweightThemeManager.getUsedTheme("firefox-compact-dark@mozilla.org");
},
},
},
};
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm
@@ -18,91 +18,91 @@ var PermissionPrompts = {
init(libDir) {
Services.prefs.setBoolPref("media.navigator.permission.fake", true);
Services.prefs.setBoolPref("extensions.install.requireBuiltInCerts", false);
Services.prefs.setBoolPref("signon.rememberSignons", true);
},
configurations: {
shareDevices: {
- selectors: ["#notification-popup"],
+ selectors: ["#notification-popup", "#identity-box"],
async applyConfig() {
await closeLastTab();
await clickOn("#webRTC-shareDevices");
},
},
shareMicrophone: {
- selectors: ["#notification-popup"],
+ selectors: ["#notification-popup", "#identity-box"],
async applyConfig() {
await closeLastTab();
await clickOn("#webRTC-shareMicrophone");
},
},
shareVideoAndMicrophone: {
- selectors: ["#notification-popup"],
+ selectors: ["#notification-popup", "#identity-box"],
async applyConfig() {
await closeLastTab();
await clickOn("#webRTC-shareDevices2");
},
},
shareScreen: {
- selectors: ["#notification-popup"],
+ selectors: ["#notification-popup", "#identity-box"],
async applyConfig() {
await closeLastTab();
await clickOn("#webRTC-shareScreen");
},
},
geo: {
- selectors: ["#notification-popup"],
+ selectors: ["#notification-popup", "#identity-box"],
async applyConfig() {
await closeLastTab();
await clickOn("#geo");
},
},
persistentStorage: {
- selectors: ["#notification-popup"],
+ selectors: ["#notification-popup", "#identity-box"],
async applyConfig() {
await closeLastTab();
await clickOn("#persistent-storage");
},
},
loginCapture: {
- selectors: ["#notification-popup"],
+ selectors: ["#notification-popup", "#identity-box"],
async applyConfig() {
await closeLastTab();
await clickOn("#login-capture");
},
},
notifications: {
- selectors: ["#notification-popup"],
+ selectors: ["#notification-popup", "#identity-box"],
async applyConfig() {
await closeLastTab();
await clickOn("#web-notifications");
},
},
addons: {
- selectors: ["#notification-popup"],
+ selectors: ["#notification-popup", "#identity-box"],
async applyConfig() {
Services.prefs.setBoolPref("xpinstall.whitelist.required", true);
await closeLastTab();
await clickOn("#addons");
},
},
addonsNoWhitelist: {
- selectors: ["#notification-popup"],
+ selectors: ["#notification-popup", "#identity-box"],
async applyConfig() {
Services.prefs.setBoolPref("xpinstall.whitelist.required", false);
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
let notification = browserWindow.document.getElementById("addon-install-confirmation-notification");
await closeLastTab();
await clickOn("#addons");
rename from build/build-clang/clang-7-mingw.json
rename to build/build-clang/clang-trunk-mingw.json
--- a/build/build-clang/clang-7-mingw.json
+++ b/build/build-clang/clang-trunk-mingw.json
@@ -1,18 +1,18 @@
{
"llvm_revision": "342383",
"stages": "3",
"build_libcxx": true,
"build_type": "Release",
"assertions": false,
- "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/tags/RELEASE_700/final",
- "clang_repo": "https://llvm.org/svn/llvm-project/cfe/tags/RELEASE_700/final",
- "lld_repo": "https://llvm.org/svn/llvm-project/lld/tags/RELEASE_700/final",
- "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/tags/RELEASE_700/final",
- "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/tags/RELEASE_700/final",
- "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/tags/RELEASE_700/final",
+ "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/trunk",
+ "clang_repo": "https://llvm.org/svn/llvm-project/cfe/trunk",
+ "lld_repo": "https://llvm.org/svn/llvm-project/lld/trunk",
+ "compiler_repo": "https://llvm.org/svn/llvm-project/compiler-rt/trunk",
+ "libcxx_repo": "https://llvm.org/svn/llvm-project/libcxx/trunk",
+ "libcxxabi_repo": "https://llvm.org/svn/llvm-project/libcxxabi/trunk",
"python_path": "/usr/bin/python2.7",
"gcc_dir": "/builds/worker/workspace/build/src/gcc",
"cc": "/builds/worker/workspace/build/src/gcc/bin/gcc",
"cxx": "/builds/worker/workspace/build/src/gcc/bin/g++",
"as": "/builds/worker/workspace/build/src/gcc/bin/gcc"
}
--- a/devtools/client/aboutdebugging-new/aboutdebugging.css
+++ b/devtools/client/aboutdebugging-new/aboutdebugging.css
@@ -1,16 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@import "chrome://global/skin/in-content/common.css";
@import "resource://devtools/client/themes/variables.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/App.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/RuntimeInfo.css";
+@import "resource://devtools/client/aboutdebugging-new/src/components/connect/ConnectPage.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/connect/ConnectSteps.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/connect/NetworkLocationsForm.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/connect/NetworkLocationsList.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetItem.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetList.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/DebugTargetPane.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/ExtensionDetail.css";
@import "resource://devtools/client/aboutdebugging-new/src/components/debugtarget/WorkerDetail.css";
--- a/devtools/client/aboutdebugging-new/index.html
+++ b/devtools/client/aboutdebugging-new/index.html
@@ -2,24 +2,14 @@
- 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/. -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="chrome://global/skin/in-content/common.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/content/aboutdebugging-new/aboutdebugging.css"/>
- <script>
- "use strict";
-
- const { BrowserLoader } =
- ChromeUtils.import("resource://devtools/client/shared/browser-loader.js", {});
- const { require } = BrowserLoader({
- baseURI: "resource://devtools/client/aboutdebugging-new/",
- window,
- });
- require("./aboutdebugging");
- </script>
+ <script type="application/javascript" src="resource://devtools/client/aboutdebugging-new/initializer.js"></script>
</head>
<body>
<div id="mount"></div>
</body>
</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/initializer.js
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { BrowserLoader } =
+ ChromeUtils.import("resource://devtools/client/shared/browser-loader.js", {});
+const { require } = BrowserLoader({
+ baseURI: "resource://devtools/client/aboutdebugging-new/",
+ window,
+});
+
+// The only purpose of this module is to load the real aboutdebugging module via the
+// BrowserLoader.
+// This cannot be done using an inline script tag in index.html because we are applying
+// CSP for about: pages in Bug 1492063.
+// And this module cannot be merged with aboutdebugging.js because modules loaded with
+// script tags are using Promises bound to the lifecycle of the document, while modules
+// loaded with a devtools loader use Promises that will still resolve if the document is
+// destroyed. This is particularly useful to ensure asynchronous destroy() calls succeed.
+require("./aboutdebugging");
--- a/devtools/client/aboutdebugging-new/moz.build
+++ b/devtools/client/aboutdebugging-new/moz.build
@@ -1,14 +1,15 @@
# 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/.
DevToolsModules(
'aboutdebugging.js',
+ 'initializer.js',
)
DIRS += [
'src',
]
XPCSHELL_TESTS_MANIFESTS += [
'test/unit/xpcshell.ini'
--- a/devtools/client/aboutdebugging-new/src/actions/ui.js
+++ b/devtools/client/aboutdebugging-new/src/actions/ui.js
@@ -1,23 +1,30 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {
+ ADB_ADDON_INSTALL_START,
+ ADB_ADDON_INSTALL_SUCCESS,
+ ADB_ADDON_INSTALL_FAILURE,
+ ADB_ADDON_UNINSTALL_START,
+ ADB_ADDON_UNINSTALL_SUCCESS,
+ ADB_ADDON_UNINSTALL_FAILURE,
ADB_ADDON_STATUS_UPDATED,
DEBUG_TARGET_COLLAPSIBILITY_UPDATED,
NETWORK_LOCATIONS_UPDATED,
PAGE_SELECTED,
PAGES,
} = require("../constants");
const NetworkLocationsModule = require("../modules/network-locations");
+const { adbAddon } = require("devtools/shared/adb/adb-addon");
const Actions = require("./index");
// XXX: Isolating the code here, because it feels wrong to rely solely on the page "not"
// being CONNECT to decide what to do. Should we have a page "type" on top of page "id"?
function _isRuntimePage(page) {
return page && page !== PAGES.CONNECT;
}
@@ -64,16 +71,44 @@ function removeNetworkLocation(location)
function updateAdbAddonStatus(adbAddonStatus) {
return { type: ADB_ADDON_STATUS_UPDATED, adbAddonStatus };
}
function updateNetworkLocations(locations) {
return { type: NETWORK_LOCATIONS_UPDATED, locations };
}
+function installAdbAddon() {
+ return async (dispatch, getState) => {
+ dispatch({ type: ADB_ADDON_INSTALL_START });
+
+ try {
+ await adbAddon.install();
+ dispatch({ type: ADB_ADDON_INSTALL_SUCCESS });
+ } catch (e) {
+ dispatch({ type: ADB_ADDON_INSTALL_FAILURE, error: e.message });
+ }
+ };
+}
+
+function uninstallAdbAddon() {
+ return async (dispatch, getState) => {
+ dispatch({ type: ADB_ADDON_UNINSTALL_START });
+
+ try {
+ await adbAddon.uninstall();
+ dispatch({ type: ADB_ADDON_UNINSTALL_SUCCESS });
+ } catch (e) {
+ dispatch({ type: ADB_ADDON_UNINSTALL_FAILURE, error: e.message });
+ }
+ };
+}
+
module.exports = {
addNetworkLocation,
+ installAdbAddon,
removeNetworkLocation,
selectPage,
+ uninstallAdbAddon,
updateAdbAddonStatus,
updateDebugTargetCollapsibility,
updateNetworkLocations,
};
--- a/devtools/client/aboutdebugging-new/src/components/App.js
+++ b/devtools/client/aboutdebugging-new/src/components/App.js
@@ -31,26 +31,35 @@ class App extends PureComponent {
networkLocations: PropTypes.arrayOf(PropTypes.string).isRequired,
networkRuntimes: PropTypes.arrayOf(Types.runtime).isRequired,
selectedPage: PropTypes.string,
usbRuntimes: PropTypes.arrayOf(Types.runtime).isRequired,
};
}
getSelectedPageComponent() {
- const { dispatch, networkLocations, selectedPage } = this.props;
+ const {
+ adbAddonStatus,
+ dispatch,
+ networkLocations,
+ selectedPage
+ } = this.props;
if (!selectedPage) {
// No page selected.
return null;
}
switch (selectedPage) {
case PAGES.CONNECT:
- return ConnectPage({ dispatch, networkLocations });
+ return ConnectPage({
+ adbAddonStatus,
+ dispatch,
+ networkLocations
+ });
default:
// All pages except for the CONNECT page are RUNTIME pages.
return RuntimePage({ dispatch });
}
}
render() {
const {
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/src/components/connect/ConnectPage.css
@@ -0,0 +1,7 @@
+/* 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/. */
+
+.connect-page__usb__toggle-button {
+ margin-top: calc(var(--base-distance) * 4);
+}
--- a/devtools/client/aboutdebugging-new/src/components/connect/ConnectPage.js
+++ b/devtools/client/aboutdebugging-new/src/components/connect/ConnectPage.js
@@ -6,28 +6,37 @@
const { createFactory, PureComponent } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
const Localized = createFactory(FluentReact.Localized);
+const {
+ USB_STATES,
+} = require("../../constants");
+
+const Actions = require("../../actions/index");
+
+loader.lazyRequireGetter(this, "ADB_ADDON_STATES", "devtools/shared/adb/adb-addon", true);
+
const ConnectSection = createFactory(require("./ConnectSection"));
const ConnectSteps = createFactory(require("./ConnectSteps"));
const NetworkLocationsForm = createFactory(require("./NetworkLocationsForm"));
const NetworkLocationsList = createFactory(require("./NetworkLocationsList"));
const USB_ICON_SRC = "chrome://devtools/skin/images/aboutdebugging-connect-icon.svg";
const WIFI_ICON_SRC = "chrome://devtools/skin/images/aboutdebugging-connect-icon.svg";
const GLOBE_ICON_SRC = "chrome://devtools/skin/images/aboutdebugging-globe-icon.svg";
class ConnectPage extends PureComponent {
static get propTypes() {
return {
+ adbAddonStatus: PropTypes.string,
dispatch: PropTypes.func.isRequired,
// Provided by wrapping the component with FluentReact.withLocalization.
getString: PropTypes.func.isRequired,
networkLocations: PropTypes.arrayOf(PropTypes.string).isRequired,
};
}
renderWifi() {
@@ -49,35 +58,101 @@ class ConnectPage extends PureComponent
getString("about-debugging-connect-wifi-step-open-options"),
getString("about-debugging-connect-wifi-step-enable-debug"),
]
})
)
);
}
+ onToggleUSBClick() {
+ const { adbAddonStatus } = this.props;
+ const isAddonInstalled = adbAddonStatus === ADB_ADDON_STATES.INSTALLED;
+ if (isAddonInstalled) {
+ this.props.dispatch(Actions.uninstallAdbAddon());
+ } else {
+ this.props.dispatch(Actions.installAdbAddon());
+ }
+ }
+
+ getUsbStatus() {
+ switch (this.props.adbAddonStatus) {
+ case ADB_ADDON_STATES.INSTALLED:
+ return USB_STATES.ENABLED_USB;
+ case ADB_ADDON_STATES.UNINSTALLED:
+ return USB_STATES.DISABLED_USB;
+ default:
+ return USB_STATES.UPDATING_USB;
+ }
+ }
+
+ renderUsbToggleButton() {
+ const usbStatus = this.getUsbStatus();
+
+ const localizedStates = {
+ [USB_STATES.ENABLED_USB]: "about-debugging-connect-usb-disable-button",
+ [USB_STATES.DISABLED_USB]: "about-debugging-connect-usb-enable-button",
+ [USB_STATES.UPDATING_USB]: "about-debugging-connect-usb-updating-button",
+ };
+ const localizedState = localizedStates[usbStatus];
+
+ // Disable the button while the USB status is updating.
+ const disabled = usbStatus === USB_STATES.UPDATING_USB;
+
+ return Localized(
+ {
+ id: localizedState
+ },
+ dom.button(
+ {
+ className: "std-button connect-page__usb__toggle-button " +
+ "js-connect-usb-toggle-button",
+ disabled,
+ onClick: () => this.onToggleUSBClick(),
+ },
+ localizedState
+ )
+ );
+ }
+
renderUsb() {
- const { getString } = this.props;
+ const { adbAddonStatus, getString } = this.props;
+ const isAddonInstalled = adbAddonStatus === ADB_ADDON_STATES.INSTALLED;
return Localized(
{
id: "about-debugging-connect-usb",
attrs: { title: true }
},
ConnectSection(
{
icon: USB_ICON_SRC,
title: "Via USB",
},
- ConnectSteps({
- steps: [
- getString("about-debugging-connect-usb-step-enable-dev-menu"),
- getString("about-debugging-connect-usb-step-enable-debug"),
- getString("about-debugging-connect-usb-step-plug-device"),
- ]
- })
+ (isAddonInstalled ?
+ ConnectSteps({
+ steps: [
+ getString("about-debugging-connect-usb-step-enable-dev-menu"),
+ getString("about-debugging-connect-usb-step-enable-debug"),
+ getString("about-debugging-connect-usb-step-plug-device"),
+ ]
+ }) :
+ Localized(
+ {
+ id: "about-debugging-connect-usb-disabled",
+ },
+ dom.aside(
+ {
+ className: "js-connect-usb-disabled-message"
+ },
+ "Enabling this will download and add the required Android USB debugging " +
+ "components to Firefox."
+ )
+ )
+ ),
+ this.renderUsbToggleButton()
)
);
}
renderNetwork() {
const { dispatch, networkLocations } = this.props;
return Localized(
{
--- a/devtools/client/aboutdebugging-new/src/components/connect/moz.build
+++ b/devtools/client/aboutdebugging-new/src/components/connect/moz.build
@@ -1,13 +1,14 @@
# 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/.
DevToolsModules(
+ 'ConnectPage.css',
'ConnectPage.js',
'ConnectSection.js',
'ConnectSteps.css',
'ConnectSteps.js',
'NetworkLocationsForm.css',
'NetworkLocationsForm.js',
'NetworkLocationsList.css',
'NetworkLocationsList.js',
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -1,15 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const actionTypes = {
+ ADB_ADDON_INSTALL_START: "ADB_ADDON_INSTALL_START",
+ ADB_ADDON_INSTALL_SUCCESS: "ADB_ADDON_INSTALL_SUCCESS",
+ ADB_ADDON_INSTALL_FAILURE: "ADB_ADDON_INSTALL_FAILURE",
+ ADB_ADDON_UNINSTALL_START: "ADB_ADDON_UNINSTALL_START",
+ ADB_ADDON_UNINSTALL_SUCCESS: "ADB_ADDON_UNINSTALL_SUCCESS",
+ ADB_ADDON_UNINSTALL_FAILURE: "ADB_ADDON_UNINSTALL_FAILURE",
ADB_ADDON_STATUS_UPDATED: "ADB_ADDON_STATUS_UPDATED",
CONNECT_RUNTIME_FAILURE: "CONNECT_RUNTIME_FAILURE",
CONNECT_RUNTIME_START: "CONNECT_RUNTIME_START",
CONNECT_RUNTIME_SUCCESS: "CONNECT_RUNTIME_SUCCESS",
DEBUG_TARGET_COLLAPSIBILITY_UPDATED: "DEBUG_TARGET_COLLAPSIBILITY_UPDATED",
DISCONNECT_RUNTIME_FAILURE: "DISCONNECT_RUNTIME_FAILURE",
DISCONNECT_RUNTIME_START: "DISCONNECT_RUNTIME_START",
DISCONNECT_RUNTIME_SUCCESS: "DISCONNECT_RUNTIME_SUCCESS",
@@ -65,17 +71,24 @@ const SERVICE_WORKER_FETCH_STATES = {
};
const SERVICE_WORKER_STATUSES = {
RUNNING: "RUNNING",
REGISTERING: "REGISTERING",
STOPPED: "STOPPED",
};
+const USB_STATES = {
+ DISABLED_USB: "DISABLED_USB",
+ ENABLED_USB: "ENABLED_USB",
+ UPDATING_USB: "UPDATING_USB",
+};
+
// flatten constants
module.exports = Object.assign({}, {
DEBUG_TARGETS,
DEBUG_TARGET_PANE,
PAGES,
RUNTIMES,
SERVICE_WORKER_FETCH_STATES,
SERVICE_WORKER_STATUSES,
+ USB_STATES,
}, actionTypes);
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -6,16 +6,18 @@ support-files =
head-addons-script.js
head.js
resources/test-adb-extension/*
resources/test-temporary-extension/*
!/devtools/client/shared/test/shared-head.js
!/devtools/client/shared/test/telemetry-test-helpers.js
[browser_aboutdebugging_connect_networklocations.js]
+[browser_aboutdebugging_connect_toggle_usb_devices.js]
+skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
[browser_aboutdebugging_debug-target-pane_collapsibilities_interaction.js]
[browser_aboutdebugging_debug-target-pane_collapsibilities_preference.js]
[browser_aboutdebugging_debug-target-pane_empty.js]
[browser_aboutdebugging_navigate.js]
[browser_aboutdebugging_sidebar_network_runtimes.js]
[browser_aboutdebugging_sidebar_usb_status.js]
skip-if = (os == 'linux' && bits == 32) # ADB start() fails on linux 32, see Bug 1499638
[browser_aboutdebugging_thisfirefox.js]
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_connect_networklocations.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_connect_networklocations.js
@@ -8,27 +8,17 @@
* Check that a network location can be added and removed.
*/
const TEST_NETWORK_LOCATION = "localhost:1111";
add_task(async function() {
const { document, tab } = await openAboutDebugging();
- const sidebarItems = document.querySelectorAll(".js-sidebar-item");
- const connectSidebarItem = [...sidebarItems].find(element => {
- return element.textContent === "Connect";
- });
- ok(connectSidebarItem, "Sidebar contains a Connect item");
-
- info("Click on the Connect item in the sidebar");
- connectSidebarItem.click();
-
- info("Wait until Connect page is displayed");
- await waitUntil(() => document.querySelector(".js-connect-page"));
+ await selectConnectPage(document);
let networkLocations = document.querySelectorAll(".js-network-location");
is(networkLocations.length, 0, "By default, no network locations are displayed");
addNetworkLocation(TEST_NETWORK_LOCATION, document);
info("Wait until the new network location is visible in the list");
await waitUntil(() => document.querySelectorAll(".js-network-location").length === 1);
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_connect_toggle_usb_devices.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { ADB } = require("devtools/shared/adb/adb");
+
+/**
+ * Check that USB Devices scanning can be enabled and disabled from the connect page.
+ */
+add_task(async function() {
+ await pushPref("devtools.remote.adb.extensionURL",
+ CHROME_URL_ROOT + "resources/test-adb-extension/adb-extension-#OS#.xpi");
+
+ const { document, tab } = await openAboutDebugging();
+
+ await selectConnectPage(document);
+
+ info("Wait until Connect page is displayed");
+ await waitUntil(() => document.querySelector(".js-connect-page"));
+
+ info("Check that by default USB devices are disabled");
+ const usbDisabledMessage = document.querySelector(".js-connect-usb-disabled-message");
+ ok(usbDisabledMessage, "A message about enabling USB devices is rendered");
+
+ const usbToggleButton = document.querySelector(".js-connect-usb-toggle-button");
+ ok(usbToggleButton, "The button to toggle USB devices debugging is rendered");
+ ok(usbToggleButton.textContent.includes("Enable"),
+ "The text of the toggle USB button is correct");
+
+ info("Click on the toggle button");
+ usbToggleButton.click();
+
+ info("Wait until the toggle button text is updated");
+ await waitUntil(() => usbToggleButton.textContent.includes("Disable"));
+ ok(!document.querySelector(".js-connect-usb-disabled-message"),
+ "The message about enabling USB devices is no longer rendered");
+
+ // Right now we are resuming as soon as "USB devices enabled" is displayed, but ADB
+ // might still be starting up. If we move to uninstall directly, the ADB startup will
+ // fail and we will have an unhandled promise rejection.
+ // See Bug 1498469.
+ info("Wait until ADB has started.");
+ await waitUntil(() => ADB.ready);
+
+ info("Click on the toggle button");
+ usbToggleButton.click();
+
+ info("Wait until the toggle button text is updated");
+ await waitUntil(() => usbToggleButton.textContent.includes("Enable"));
+ ok(document.querySelector(".js-connect-usb-disabled-message"),
+ "The message about enabling USB devices is rendered again");
+
+ await removeTab(tab);
+});
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_sidebar_usb_status.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_sidebar_usb_status.js
@@ -6,27 +6,16 @@
const { adbAddon } = require("devtools/shared/adb/adb-addon");
const { ADB } = require("devtools/shared/adb/adb");
/**
* This test asserts that the sidebar shows a message describing the status of the USB
* devices scanning.
*/
add_task(async function() {
- // Make sure the ADB addon is removed and ADB is stopped when the test ends.
- registerCleanupFunction(async function() {
- try {
- await adbAddon.uninstall();
- } catch (e) {
- // Will throw if the addon is already uninstalled, ignore exceptions here.
- }
-
- await ADB.kill();
- });
-
await pushPref("devtools.remote.adb.extensionURL",
CHROME_URL_ROOT + "resources/test-adb-extension/adb-extension-#OS#.xpi");
const { document, tab } = await openAboutDebugging();
const usbStatusElement = document.querySelector(".js-sidebar-usb-status");
ok(usbStatusElement, "Sidebar shows the USB status element");
ok(usbStatusElement.textContent.includes("USB devices disabled"),
--- a/devtools/client/aboutdebugging-new/test/browser/head.js
+++ b/devtools/client/aboutdebugging-new/test/browser/head.js
@@ -12,16 +12,28 @@
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
this);
// Load collapsibilities helpers
Services.scriptloader.loadSubScript(
CHROME_URL_ROOT + "debug-target-pane_collapsibilities_head.js", this);
+// Make sure the ADB addon is removed and ADB is stopped when the test ends.
+registerCleanupFunction(async function() {
+ try {
+ const { adbAddon } = require("devtools/shared/adb/adb-addon");
+ await adbAddon.uninstall();
+ } catch (e) {
+ // Will throw if the addon is already uninstalled, ignore exceptions here.
+ }
+ const { ADB } = require("devtools/shared/adb/adb");
+ await ADB.kill();
+});
+
/**
* Enable the new about:debugging panel.
*/
async function enableNewAboutDebugging() {
await pushPref("devtools.aboutdebugging.new-enabled", true);
}
async function openAboutDebugging(page, win) {
@@ -37,14 +49,31 @@ async function openAboutDebugging(page,
await waitUntil(() => document.querySelector(".app"));
info("Wait until the client connection was established");
await waitUntil(() => document.querySelector(".js-runtime-page"));
return { tab, document, window };
}
+/**
+ * Navigate to the Connect page. Resolves when the Connect page is rendered.
+ */
+async function selectConnectPage(doc) {
+ const sidebarItems = doc.querySelectorAll(".js-sidebar-item");
+ const connectSidebarItem = [...sidebarItems].find(element => {
+ return element.textContent === "Connect";
+ });
+ ok(connectSidebarItem, "Sidebar contains a Connect item");
+
+ info("Click on the Connect item in the sidebar");
+ connectSidebarItem.click();
+
+ info("Wait until Connect page is displayed");
+ await waitUntil(() => doc.querySelector(".js-connect-page"));
+}
+
function findSidebarItemByText(text, document) {
const sidebarItems = document.querySelectorAll(".js-sidebar-item");
return [...sidebarItems].find(element => {
return element.textContent.includes(text);
});
}
--- a/devtools/client/aboutdebugging-new/tmp-locale/en-US/aboutdebugging.notftl
+++ b/devtools/client/aboutdebugging-new/tmp-locale/en-US/aboutdebugging.notftl
@@ -54,16 +54,21 @@ about-debugging-connect-wifi-step-open-o
# WiFi section step by step guide
about-debugging-connect-wifi-step-enable-debug = Enable Remote Debugging via WiFi in the Developer Tools section
# USB section of the Connect page
about-debugging-connect-usb
.title = Via USB
+about-debugging-connect-usb-disabled = Enabling this will download and add the required Android USB debugging components to Firefox.
+about-debugging-connect-usb-enable-button = Enable USB Devices
+about-debugging-connect-usb-disable-button = Disable USB Devices
+about-debugging-connect-usb-updating-button = Updating…
+
# USB section step by step guide
about-debugging-connect-usb-step-enable-dev-menu = Enable Developer menu on your Android device
# USB section step by step guide
about-debugging-connect-usb-step-enable-debug = Enable USB Debugging on the Android Developer Menu
# USB section step by step guide
about-debugging-connect-usb-step-plug-device = Connect the USB Device to your computer
deleted file mode 100644
--- a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension-nobg/manifest.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "manifest_version": 2,
- "name": "test-devtools-webextension-nobg",
- "version": "1.0",
- "applications": {
- "gecko": {
- "id": "test-devtools-webextension-nobg@mozilla.org"
- }
- }
-}
deleted file mode 100644
--- a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension-noid/manifest.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "manifest_version": 2,
- "name": "test-devtools-webextension-noid",
- "version": "1.0"
-}
deleted file mode 100644
--- a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension-unknown-prop/manifest.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "manifest_version": 2,
- "name": "test-devtools-webextension-unknown-prop",
- "version": "1.0",
- "applications": {
- "gecko": {
- "id": "test-devtools-webextension-unknown-prop@mozilla.org"
- }
- },
- "browser_actions": {
- "default_title": "WebExtension Popup Debugging",
- "default_popup": "popup.html"
- }
-}
deleted file mode 100644
--- a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/bg.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/* eslint-env browser */
-/* global browser */
-
-"use strict";
-
-document.body.innerText = "Background Page Body Test Content";
-
-// These functions are called from the following about:debugging tests:
-// - browser_addons_debug_webextension.js
-// - browser_addons_debug_webextension_popup.js
-
-// eslint-disable-next-line no-unused-vars
-function myWebExtensionAddonFunction() {
- console.log("Background page function called", browser.runtime.getManifest());
-}
-
-// eslint-disable-next-line no-unused-vars
-function myWebExtensionShowPopup() {
- browser.test.sendMessage("readyForOpenPopup");
-}
deleted file mode 100644
--- a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/manifest.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "manifest_version": 2,
- "name": "test-devtools-webextension",
- "version": "1.0",
- "applications": {
- "gecko": {
- "id": "test-devtools-webextension@mozilla.org"
- }
- },
- "background": {
- "scripts": ["bg.js"]
- },
- "browser_action": {
- "default_title": "WebExtension Popup Debugging",
- "default_popup": "popup.html"
- }
-}
deleted file mode 100644
--- a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <meta charset="utf-8">
- <script src="popup.js"></script>
- </head>
- <body>
- Background Page Body Test Content
- </body>
-</html>
deleted file mode 100644
--- a/devtools/client/aboutdebugging/test/addons/test-devtools-webextension/popup.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/* eslint-env browser */
-/* global browser */
-
-"use strict";
-
-// This function is called from the following about:debugging test:
-// browser_addons_debug_webextension.js
-//
-// eslint-disable-next-line no-unused-vars
-function myWebExtensionPopupAddonFunction() {
- browser.test.sendMessage("popupPageFunctionCalled", browser.runtime.getManifest());
-}
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_info.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_info.js
@@ -34,25 +34,37 @@ add_task(async function testLegacyAddon(
});
add_task(async function testWebExtension() {
const addonId = "test-devtools-webextension-nobg@mozilla.org";
const addonName = "test-devtools-webextension-nobg";
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
+
+ const addonFile = ExtensionTestCommon.generateXPI({
+ manifest: {
+ name: addonName,
+ applications: {
+ gecko: {id: addonId},
+ },
+ },
+ });
+ registerCleanupFunction(() => addonFile.remove(false));
+
await installAddon({
document,
- path: "addons/test-devtools-webextension-nobg/manifest.json",
+ file: addonFile,
name: addonName,
isWebExtension: true
});
const container = document.querySelector(`[data-addon-id="${addonId}"]`);
- testFilePath(container, "/test/addons/test-devtools-webextension-nobg/");
+
+ testFilePath(container, addonFile.leafName);
const extensionID = container.querySelector(".extension-id span");
ok(extensionID.textContent === "test-devtools-webextension-nobg@mozilla.org");
const internalUUID = container.querySelector(".internal-uuid span");
ok(internalUUID.textContent.match(UUID_REGEX), "internalUUID is correct");
const manifestURL = container.querySelector(".manifest-url");
@@ -63,19 +75,27 @@ add_task(async function testWebExtension
await closeAboutDebugging(tab);
});
add_task(async function testTemporaryWebExtension() {
const addonName = "test-devtools-webextension-noid";
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
+
+ const addonFile = ExtensionTestCommon.generateXPI({
+ manifest: {
+ name: addonName,
+ },
+ });
+ registerCleanupFunction(() => addonFile.remove(false));
+
await installAddon({
document,
- path: "addons/test-devtools-webextension-noid/manifest.json",
+ file: addonFile,
name: addonName,
isWebExtension: true
});
const addons =
document.querySelectorAll("#temporary-extensions .addon-target-container");
// Assuming that our temporary add-on is now at the top.
const container = addons[addons.length - 1];
@@ -93,32 +113,45 @@ add_task(async function testTemporaryWeb
});
add_task(async function testUnknownManifestProperty() {
const addonId = "test-devtools-webextension-unknown-prop@mozilla.org";
const addonName = "test-devtools-webextension-unknown-prop";
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
+
+ const addonFile = ExtensionTestCommon.generateXPI({
+ manifest: {
+ name: addonName,
+ applications: {
+ gecko: {id: addonId},
+ },
+ wrong_manifest_property_name: {
+ }
+ },
+ });
+ registerCleanupFunction(() => addonFile.remove(false));
+
await installAddon({
document,
- path: "addons/test-devtools-webextension-unknown-prop/manifest.json",
+ file: addonFile,
name: addonName,
isWebExtension: true
});
info("Wait until the addon appears in about:debugging");
const container = await waitUntilAddonContainer(addonName, document);
info("Wait until the installation message appears for the new addon");
await waitUntilElement(".addon-target-messages", container);
const messages = container.querySelectorAll(".addon-target-message");
ok(messages.length === 1, "there is one message");
- ok(messages[0].textContent.match(/Error processing browser_actions/),
+ ok(messages[0].textContent.match(/Error processing wrong_manifest_property_name/),
"the message is helpful");
ok(messages[0].classList.contains("addon-target-warning-message"),
"the message is a warning");
await uninstallAddon({document, id: addonId, name: addonName});
await closeAboutDebugging(tab);
});
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension.js
@@ -8,31 +8,46 @@
const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
requestLongerTimeout(2);
const ADDON_ID = "test-devtools-webextension@mozilla.org";
const ADDON_NAME = "test-devtools-webextension";
-const ADDON_MANIFEST_PATH = "addons/test-devtools-webextension/manifest.json";
const {
BrowserToolboxProcess
} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
/**
* This test file ensures that the webextension addon developer toolbox:
* - when the debug button is clicked on a webextension, the opened toolbox
* has a working webconsole with the background page as default target;
*/
add_task(async function testWebExtensionsToolboxWebConsole() {
+ const addonFile = ExtensionTestCommon.generateXPI({
+ background: function() {
+ window.myWebExtensionAddonFunction = function() {
+ console.log("Background page function called",
+ this.browser.runtime.getManifest());
+ };
+ },
+ manifest: {
+ name: ADDON_NAME,
+ applications: {
+ gecko: {id: ADDON_ID},
+ },
+ },
+ });
+ registerCleanupFunction(() => addonFile.remove(false));
+
const {
tab, document, debugBtn,
- } = await setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_MANIFEST_PATH);
+ } = await setupTestAboutDebuggingWebExtension(ADDON_NAME, addonFile);
// Be careful, this JS function is going to be executed in the addon toolbox,
// which lives in another process. So do not try to use any scope variable!
const env = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
const testScript = function() {
/* eslint-disable no-undef */
function findMessages(hud, text, selector = ".message") {
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_inspector.js
@@ -7,31 +7,43 @@
const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
requestLongerTimeout(2);
const ADDON_ID = "test-devtools-webextension@mozilla.org";
const ADDON_NAME = "test-devtools-webextension";
-const ADDON_PATH = "addons/test-devtools-webextension/manifest.json";
const {
BrowserToolboxProcess
} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
/**
* This test file ensures that the webextension addon developer toolbox:
* - the webextension developer toolbox has a working Inspector panel, with the
* background page as default target;
*/
add_task(async function testWebExtensionsToolboxInspector() {
+ const addonFile = ExtensionTestCommon.generateXPI({
+ background: function() {
+ document.body.innerText = "Background Page Body Test Content";
+ },
+ manifest: {
+ name: ADDON_NAME,
+ applications: {
+ gecko: {id: ADDON_ID},
+ },
+ },
+ });
+ registerCleanupFunction(() => addonFile.remove(false));
+
const {
tab, document, debugBtn,
- } = await setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_PATH);
+ } = await setupTestAboutDebuggingWebExtension(ADDON_NAME, addonFile);
// Be careful, this JS function is going to be executed in the addon toolbox,
// which lives in another process. So do not try to use any scope variable!
const env = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
const testScript = function() {
/* eslint-disable no-undef */
toolbox.selectTool("inspector")
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_nobg.js
@@ -7,75 +7,80 @@
const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
requestLongerTimeout(2);
const ADDON_NOBG_ID = "test-devtools-webextension-nobg@mozilla.org";
const ADDON_NOBG_NAME = "test-devtools-webextension-nobg";
-const ADDON_NOBG_PATH = "addons/test-devtools-webextension-nobg/manifest.json";
const {
BrowserToolboxProcess
} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
/**
* This test file ensures that the webextension addon developer toolbox:
* - the webextension developer toolbox is connected to a fallback page when the
* background page is not available (and in the fallback page document body contains
* the expected message, which warns the user that the current page is not a real
* webextension context);
*/
add_task(async function testWebExtensionsToolboxNoBackgroundPage() {
+ const addonFile = ExtensionTestCommon.generateXPI({
+ manifest: {
+ name: ADDON_NOBG_NAME,
+ applications: {
+ gecko: {id: ADDON_NOBG_ID},
+ },
+ },
+ });
+ registerCleanupFunction(() => addonFile.remove(false));
+
const {
tab, document, debugBtn,
- } = await setupTestAboutDebuggingWebExtension(ADDON_NOBG_NAME, ADDON_NOBG_PATH);
+ } = await setupTestAboutDebuggingWebExtension(ADDON_NOBG_NAME, addonFile);
// Be careful, this JS function is going to be executed in the addon toolbox,
// which lives in another process. So do not try to use any scope variable!
const env = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
const testScript = function() {
/* eslint-disable no-undef */
- toolbox.selectTool("inspector")
- .then(inspector => {
- return inspector.walker.querySelector(inspector.walker.rootNode, "body");
- })
- .then((nodeActor) => {
- if (!nodeActor) {
- throw new Error("nodeActor not found");
- }
+ toolbox.selectTool("inspector").then(async inspector => {
+ const nodeActor = await inspector.walker.querySelector(
+ inspector.walker.rootNode, "body");
- dump("Got a nodeActor\n");
+ if (!nodeActor) {
+ throw new Error("nodeActor not found");
+ }
- if (!(nodeActor.inlineTextChild)) {
- throw new Error("inlineTextChild not found");
- }
+ if (!(nodeActor.inlineTextChild)) {
+ throw new Error("inlineTextChild not found");
+ }
- dump("Got a nodeActor with an inline text child\n");
+ dump("Got a nodeActor with an inline text child\n");
- const expectedValue = "Your addon does not have any document opened yet.";
- const actualValue = nodeActor.inlineTextChild._form.nodeValue;
+ const expectedValue = "Your addon does not have any document opened yet.";
+ const actualValue = nodeActor.inlineTextChild._form.nodeValue;
- if (actualValue !== expectedValue) {
- throw new Error(
- `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
- );
- }
+ if (actualValue !== expectedValue) {
+ throw new Error(
+ `mismatched inlineTextchild value: "${actualValue}" !== "${expectedValue}"`
+ );
+ }
- dump("Got the expected inline text content in the selected node\n");
- return Promise.resolve();
- })
- .then(() => toolbox.destroy())
- .catch((error) => {
- dump("Error while running code in the browser toolbox process:\n");
- dump(error + "\n");
- dump("stack:\n" + error.stack + "\n");
- });
+ dump("Got the expected inline text content in the selected node\n");
+
+ await toolbox.destroy();
+ }).catch((error) => {
+ dump("Error while running code in the browser toolbox process:\n");
+ dump(error + "\n");
+ dump("stack:\n" + error.stack + "\n");
+ });
/* eslint-enable no-undef */
};
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
registerCleanupFunction(() => {
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
});
const onToolboxClose = BrowserToolboxProcess.once("close");
--- a/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_debug_webextension_popup.js
@@ -7,17 +7,16 @@
const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
PromiseTestUtils.whitelistRejectionsGlobally(/File closed/);
// Avoid test timeouts that can occur while waiting for the "addon-console-works" message.
requestLongerTimeout(2);
const ADDON_ID = "test-devtools-webextension@mozilla.org";
const ADDON_NAME = "test-devtools-webextension";
-const ADDON_MANIFEST_PATH = "addons/test-devtools-webextension/manifest.json";
const {
BrowserToolboxProcess
} = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
/**
* This test file ensures that the webextension addon developer toolbox:
* - when the debug button is clicked on a webextension, the opened toolbox
@@ -37,16 +36,56 @@ const {
* Returns the widget id for an extension with the passed id.
*/
function makeWidgetId(id) {
id = id.toLowerCase();
return id.replace(/[^a-z0-9_-]/g, "_");
}
add_task(async function testWebExtensionsToolboxSwitchToPopup() {
+ const addonFile = ExtensionTestCommon.generateXPI({
+ background: function() {
+ const {browser} = this;
+ window.myWebExtensionShowPopup = function() {
+ browser.test.sendMessage("readyForOpenPopup");
+ };
+ },
+ manifest: {
+ name: ADDON_NAME,
+ applications: {
+ gecko: {id: ADDON_ID},
+ },
+ browser_action: {
+ default_title: "WebExtension Popup Debugging",
+ default_popup: "popup.html",
+ },
+ },
+ files: {
+ "popup.html": `<!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ <script src="popup.js"></script>
+ </head>
+ <body>
+ Background Page Body Test Content
+ </body>
+ </html>
+ `,
+ "popup.js": function() {
+ const {browser} = this;
+ window.myWebExtensionPopupAddonFunction = function() {
+ browser.test.sendMessage("popupPageFunctionCalled",
+ browser.runtime.getManifest());
+ };
+ },
+ },
+ });
+ registerCleanupFunction(() => addonFile.remove(false));
+
let onReadyForOpenPopup;
let onPopupCustomMessage;
is(Services.prefs.getBoolPref("ui.popup.disable_autohide"), false,
"disable_autohide shoult be initially false");
Management.on("startup", function listener(event, extension) {
if (extension.name != ADDON_NAME) {
@@ -76,17 +115,17 @@ add_task(async function testWebExtension
// Wait for a notification sent by a script evaluated the test addon via
// the web console.
onPopupCustomMessage = waitForExtensionTestMessage("popupPageFunctionCalled");
});
const {
tab, document, debugBtn,
- } = await setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_MANIFEST_PATH);
+ } = await setupTestAboutDebuggingWebExtension(ADDON_NAME, addonFile);
// Be careful, this JS function is going to be executed in the addon toolbox,
// which lives in another process. So do not try to use any scope variable!
const env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
const testScript = function() {
/* eslint-disable no-undef */
--- a/devtools/client/aboutdebugging/test/browser_addons_remove.js
+++ b/devtools/client/aboutdebugging/test/browser_addons_remove.js
@@ -39,20 +39,30 @@ add_task(async function removeLegacyExte
add_task(async function removeWebextension() {
const addonID = "test-devtools-webextension@mozilla.org";
const addonName = "test-devtools-webextension";
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
+ const addonFile = ExtensionTestCommon.generateXPI({
+ manifest: {
+ name: addonName,
+ applications: {
+ gecko: {id: addonID},
+ },
+ },
+ });
+ registerCleanupFunction(() => addonFile.remove(false));
+
// Install this add-on, and verify that it appears in the about:debugging UI
await installAddon({
document,
- path: "addons/test-devtools-webextension/manifest.json",
+ file: addonFile,
name: addonName,
isWebExtension: true,
});
ok(getTargetEl(document, addonID), "add-on is shown");
info("Click on the remove button and wait until the addon container is removed");
getRemoveButton(document, addonID).click();
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -9,16 +9,17 @@
// Load the shared-head file first.
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js",
this);
const { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm", {});
const { Management } = ChromeUtils.import("resource://gre/modules/Extension.jsm", {});
+const { ExtensionTestCommon } = ChromeUtils.import("resource://testing-common/ExtensionTestCommon.jsm", {});
async function openAboutDebugging(page, win) {
info("opening about:debugging");
let url = "about:debugging";
if (page) {
url += "#" + page;
}
@@ -160,22 +161,26 @@ async function waitUntilElement(selector
* @param {DOMDocument} document #tabs section container document
* @return {DOMNode} target list or container element
*/
function getTabList(document) {
return document.querySelector("#tabs .target-list") ||
document.querySelector("#tabs.targets");
}
-async function installAddon({document, path, name, isWebExtension}) {
+async function installAddon({document, path, file, name, isWebExtension}) {
// Mock the file picker to select a test addon
const MockFilePicker = SpecialPowers.MockFilePicker;
MockFilePicker.init(window);
- const file = getSupportsFile(path);
- MockFilePicker.setFiles([file.file]);
+ if (path) {
+ file = getSupportsFile(path);
+ MockFilePicker.setFiles([file.file]);
+ } else {
+ MockFilePicker.setFiles([file]);
+ }
let onAddonInstalled;
if (isWebExtension) {
onAddonInstalled = new Promise(done => {
Management.on("startup", function listener(event, extension) {
if (extension.name != name) {
return;
@@ -336,17 +341,17 @@ function waitForDelayedStartupFinished(w
}
}, "browser-delayed-startup-finished");
});
}
/**
* open the about:debugging page and install an addon
*/
-async function setupTestAboutDebuggingWebExtension(name, path) {
+async function setupTestAboutDebuggingWebExtension(name, file) {
await new Promise(resolve => {
const options = {"set": [
// Force enabling of addons debugging
["devtools.chrome.enabled", true],
["devtools.debugger.remote-enabled", true],
// Disable security prompt
["devtools.debugger.prompt-connection", false],
// Enable Browser toolbox test script execution via env variable
@@ -355,17 +360,17 @@ async function setupTestAboutDebuggingWe
SpecialPowers.pushPrefEnv(options, resolve);
});
const { tab, document } = await openAboutDebugging("addons");
await waitForInitialAddonList(document);
await installAddon({
document,
- path,
+ file,
name,
isWebExtension: true,
});
// Retrieve the DEBUG button for the addon
const names = getInstalledAddonNames(document);
const nameEl = names.filter(element => element.textContent === name)[0];
ok(name, "Found the addon in the list");
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -474,18 +474,18 @@ Workers.prototype = {
connect: function () {
if (!Prefs.workersEnabled) {
return;
}
this._updateWorkerList();
- // `_targetFront` can be BrowsingContextTargetFront (protocol.js front) or
- // WorkerClient/DebuggerClient (old fashion client)
+ // `_targetFront` can be BrowsingContextTargetFront/WorkerTargetFront (protocol.js
+ // front) or DebuggerClient (old fashion client)
if (typeof(this._targetFront.on) == "function") {
this._targetFront.on("workerListChanged", this._onWorkerListChanged);
} else {
this._targetFront.addListener("workerListChanged", this._onWorkerListChanged);
}
},
disconnect: function () {
@@ -524,18 +524,18 @@ Workers.prototype = {
});
},
_onWorkerListChanged: function () {
this._updateWorkerList();
},
_onWorkerSelect: function (workerForm) {
- DebuggerController.client.attachWorker(workerForm.actor).then(([response, workerClient]) => {
- let toolbox = gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
+ DebuggerController.client.attachWorker(workerForm.actor).then(([response, workerTargetFront]) => {
+ let toolbox = gDevTools.showToolbox(TargetFactory.forWorker(workerTargetFront),
"jsdebugger", Toolbox.HostType.WINDOW);
window.emit(EVENTS.WORKER_SELECTED, toolbox);
});
}
};
/**
* ThreadState keeps the UI up to date with the state of the
--- a/devtools/client/debugger/new/panel.js
+++ b/devtools/client/debugger/new/panel.js
@@ -59,21 +59,21 @@ DebuggerPanel.prototype = {
return this._store.getState();
},
openLink: function(url) {
openContentLink(url);
},
openWorkerToolbox: async function(worker) {
- const [response, workerClient] =
+ const [response, workerTargetFront] =
await this.toolbox.target.client.attachWorker(worker.actor);
- const workerTarget = TargetFactory.forWorker(workerClient);
+ const workerTarget = TargetFactory.forWorker(workerTargetFront);
const toolbox = await gDevTools.showToolbox(workerTarget, "jsdebugger", Toolbox.HostType.WINDOW);
- toolbox.once("destroy", () => workerClient.detach());
+ toolbox.once("destroy", () => workerTargetFront.detach());
},
getFrames: function() {
let frames = this._selectors.getFrames(this._getState());
// Frames is null when the debugger is not paused.
if (!frames) {
return {
--- a/devtools/client/debugger/new/src/client/firefox/events.js
+++ b/devtools/client/debugger/new/src/client/firefox/events.js
@@ -36,18 +36,18 @@ function setupEvents(dependencies) {
});
if (threadClient) {
Object.keys(clientEvents).forEach(eventName => {
threadClient.addListener(eventName, clientEvents[eventName]);
});
if (threadClient._parent) {
- // Parent may be BrowsingContextTargetFront and be protocol.js.
- // Or DebuggerClient/WorkerClient and still be old fashion actor.
+ // Parent may be BrowsingContextTargetFront/WorkerTargetFront and be protocol.js.
+ // Or DebuggerClient and still be old fashion actor.
if (threadClient._parent.on) {
threadClient._parent.on("workerListChanged", workerListChanged);
} else {
threadClient._parent.addListener("workerListChanged", workerListChanged);
}
}
}
}
--- a/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js
@@ -23,20 +23,20 @@ add_task(async function() {
let tab = await addTab(TAB_URL);
let { tabs } = await listTabs(client);
let [, targetFront] = await attachTarget(client, findTab(tabs, TAB_URL));
await listWorkers(targetFront);
await createWorkerInTab(tab, WORKER_URL);
let { workers } = await listWorkers(targetFront);
- let [, workerClient] = await attachWorker(targetFront,
+ let [, workerTargetFront] = await attachWorker(targetFront,
findWorker(workers, WORKER_URL));
- let toolbox = await gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
+ let toolbox = await gDevTools.showToolbox(TargetFactory.forWorker(workerTargetFront),
"jsdebugger",
Toolbox.HostType.WINDOW);
is(toolbox.hostType, "window", "correct host");
await new Promise(done => {
toolbox.win.parent.addEventListener("message", function onmessage(event) {
if (event.data.name == "set-host-title") {
@@ -50,13 +50,13 @@ add_task(async function() {
let toolTabs = toolbox.doc.querySelectorAll(".devtools-tab");
let activeTools = [...toolTabs].map(tab=>tab.getAttribute("data-id"));
is(activeTools.join(","), "webconsole,jsdebugger,scratchpad",
"Correct set of tools supported by worker");
terminateWorkerInTab(tab, WORKER_URL);
- await waitForWorkerClose(workerClient);
+ await waitForWorkerClose(workerTargetFront);
await close(client);
await toolbox.destroy();
});
--- a/devtools/client/debugger/test/mochitest/head.js
+++ b/devtools/client/debugger/test/mochitest/head.js
@@ -1109,24 +1109,24 @@ function attachWorker(targetFront, worke
return targetFront.attachWorker(worker.actor);
}
function waitForWorkerListChanged(targetFront) {
info("Waiting for worker list to change.");
return targetFront.once("workerListChanged");
}
-function attachThread(workerClient, options) {
+function attachThread(workerTargetFront, options) {
info("Attaching to thread.");
- return workerClient.attachThread(options);
+ return workerTargetFront.attachThread(options);
}
-async function waitForWorkerClose(workerClient) {
+async function waitForWorkerClose(workerTargetFront) {
info("Waiting for worker to close.");
- await workerClient.once("close");
+ await workerTargetFront.once("close");
info("Worker did close.");
}
function resume(threadClient) {
info("Resuming thread.");
return threadClient.resume();
}
@@ -1283,20 +1283,20 @@ async function initWorkerDebugger(TAB_UR
let tab = await addTab(TAB_URL);
let { tabs } = await listTabs(client);
let [, targetFront] = await attachTarget(client, findTab(tabs, TAB_URL));
await createWorkerInTab(tab, WORKER_URL);
let { workers } = await listWorkers(targetFront);
- let [, workerClient] = await attachWorker(targetFront,
+ let [, workerTargetFront] = await attachWorker(targetFront,
findWorker(workers, WORKER_URL));
- let toolbox = await gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
+ let toolbox = await gDevTools.showToolbox(TargetFactory.forWorker(workerTargetFront),
"jsdebugger",
Toolbox.HostType.WINDOW);
let debuggerPanel = toolbox.getCurrentPanel();
let gDebugger = debuggerPanel.panelWin;
- return {client, tab, targetFront, workerClient, toolbox, gDebugger};
+ return {client, tab, targetFront, workerTargetFront, toolbox, gDebugger};
}
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -370,20 +370,20 @@ var gDevToolsBrowser = exports.gDevTools
* Open a window-hosted toolbox to debug the worker associated to the provided
* worker actor.
*
* @param {DebuggerClient} client
* @param {Object} workerTargetActor
* worker actor form to debug
*/
async openWorkerToolbox(client, workerTargetActor) {
- const [, workerClient] = await client.attachWorker(workerTargetActor);
- const workerTarget = TargetFactory.forWorker(workerClient);
+ const [, workerTargetFront] = await client.attachWorker(workerTargetActor);
+ const workerTarget = TargetFactory.forWorker(workerTargetFront);
const toolbox = await gDevTools.showToolbox(workerTarget, null, Toolbox.HostType.WINDOW);
- toolbox.once("destroy", () => workerClient.detach());
+ toolbox.once("destroy", () => workerTargetFront.detach());
},
/**
* Install WebIDE widget
*/
// Used by itself
installWebIDEWidget() {
if (this.isWebIDEWidgetInstalled()) {
--- a/devtools/client/framework/moz.build
+++ b/devtools/client/framework/moz.build
@@ -1,15 +1,18 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
-BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+BROWSER_CHROME_MANIFESTS += [
+ 'test/browser.ini',
+ 'test/metrics/browser_metrics_inspector.ini'
+]
XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
DIRS += [
'components',
]
DevToolsModules(
'attach-thread.js',
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -115,21 +115,21 @@ const TargetFactory = exports.TargetFact
if (targetPromise == null) {
const target = new TabTarget(options);
targetPromise = target.attach().then(() => target);
promiseTargets.set(options, targetPromise);
}
return targetPromise;
},
- forWorker: function(workerClient) {
- let target = targets.get(workerClient);
+ forWorker: function(workerTargetFront) {
+ let target = targets.get(workerTargetFront);
if (target == null) {
- target = new WorkerTarget(workerClient);
- targets.set(workerClient, target);
+ target = new WorkerTarget(workerTargetFront);
+ targets.set(workerTargetFront, target);
}
return target;
},
/**
* Creating a target for a tab that is being closed is a problem because it
* allows a leak as a result of coming after the close event which normally
* clears things up. This function allows us to ask if there is a known
@@ -843,29 +843,29 @@ TabTarget.prototype = {
logWarningInPage: function(text, category) {
if (this.activeTab && this.activeTab.traits.logInPage) {
const warningFlag = 1;
this.activeTab.logInPage({ text, category, flags: warningFlag });
}
},
};
-function WorkerTarget(workerClient) {
+function WorkerTarget(workerTargetFront) {
EventEmitter.decorate(this);
- this._workerClient = workerClient;
+ this._workerTargetFront = workerTargetFront;
}
/**
* A WorkerTarget represents a worker. Unlike TabTarget, which can represent
* either a local or remote tab, WorkerTarget always represents a remote worker.
* Moreover, unlike TabTarget, which is constructed with a placeholder object
* for remote tabs (from which a TargetFront can then be lazily obtained),
- * WorkerTarget is constructed with a WorkerClient directly.
+ * WorkerTarget is constructed with a WorkerTargetFront directly.
*
- * WorkerClient is designed to mimic the interface of TargetFront as closely as
+ * WorkerTargetFront is designed to mimic the interface of TargetFront as closely as
* possible. This allows us to debug workers as if they were ordinary tabs,
* requiring only minimal changes to the rest of the frontend.
*/
WorkerTarget.prototype = {
get isRemote() {
return true;
},
@@ -873,47 +873,47 @@ WorkerTarget.prototype = {
return true;
},
get name() {
return "Worker";
},
get url() {
- return this._workerClient.url;
+ return this._workerTargetFront.url;
},
get isWorkerTarget() {
return true;
},
get form() {
return {
- consoleActor: this._workerClient.consoleActor
+ consoleActor: this._workerTargetFront.consoleActor
};
},
get activeTab() {
- return this._workerClient;
+ return this._workerTargetFront;
},
get activeConsole() {
return this.client._clients.get(this.form.consoleActor);
},
get client() {
- return this._workerClient.client;
+ return this._workerTargetFront.client;
},
get canRewind() {
return false;
},
destroy: function() {
- this._workerClient.detach();
+ this._workerTargetFront.detach();
},
hasActor: function(name) {
// console is the only one actor implemented by WorkerTargetActor
if (name == "console") {
return true;
}
return false;
--- a/devtools/client/framework/test/browser_toolbox_window_reload_target.js
+++ b/devtools/client/framework/test/browser_toolbox_window_reload_target.js
@@ -1,98 +1,103 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
+// Test that pressing various page reload keyboard shortcuts always works when devtools
+// has focus, no matter if it's undocked or docked, and whatever the tool selected (this
+// is to avoid tools from overriding the page reload shortcuts).
+// This test also serves as a safety net checking that tools just don't explode when the
+// page is reloaded.
+// It is therefore quite long to run.
+
requestLongerTimeout(10);
const TEST_URL = "data:text/html;charset=utf-8," +
"<html><head><title>Test reload</title></head>" +
"<body><h1>Testing reload from devtools</h1></body></html>";
-var {Toolbox} = require("devtools/client/framework/toolbox");
-
-const {LocalizationHelper} = require("devtools/shared/l10n");
+const { Toolbox } = require("devtools/client/framework/toolbox");
+const { LocalizationHelper } = require("devtools/shared/l10n");
const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
-var target, toolbox, description, reloadsSent, toolIDs;
+// Track how many page reloads we've sent to the page.
+var reloadsSent = 0;
-function test() {
- addTab(TEST_URL).then(async () => {
- target = await TargetFactory.forTab(gBrowser.selectedTab);
+add_task(async function() {
+ await addTab(TEST_URL);
+ const target = await TargetFactory.forTab(gBrowser.selectedTab);
+ // Load the frame-script-utils into the child.
+ loadFrameScriptUtils();
- toolIDs = gDevTools.getToolDefinitionArray()
- .filter(def => def.isTargetSupported(target))
- .map(def => def.id);
- gDevTools.showToolbox(target, toolIDs[0], Toolbox.HostType.BOTTOM)
- .then(startReloadTest);
- });
-}
+ info("Getting the entire list of tools supported in this tab");
+ const toolIDs = gDevTools.getToolDefinitionArray()
+ .filter(def => def.isTargetSupported(target))
+ .map(def => def.id);
-function startReloadTest(aToolbox) {
- loadFrameScriptUtils(); // causes frame-script-utils to be loaded into the child.
- toolbox = aToolbox;
+ info("Display the toolbox, docked at the bottom, with the first tool selected");
+ const toolbox = await gDevTools.showToolbox(target, toolIDs[0],
+ Toolbox.HostType.BOTTOM);
- reloadsSent = 0;
- let reloads = 0;
- const reloadCounter = (msg) => {
- reloads++;
- info("Detected reload #" + reloads);
- is(reloads, reloadsSent, "Reloaded from devtools window once and only for " + description + "");
+ info("Listen to page reloads to check that they are indeed sent by the toolbox");
+ let reloadDetected = 0;
+ const reloadCounter = msg => {
+ reloadDetected++;
+ info("Detected reload #" + reloadDetected);
+ is(reloadDetected, reloadsSent, "Detected the right number of reloads in the page");
};
gBrowser.selectedBrowser.messageManager.addMessageListener("devtools:test:load", reloadCounter);
- testAllTheTools("docked", () => {
- const origHostType = toolbox.hostType;
- toolbox.switchHost(Toolbox.HostType.WINDOW).then(() => {
- toolbox.win.focus();
- testAllTheTools("undocked", () => {
- toolbox.switchHost(origHostType).then(() => {
- gBrowser.selectedBrowser.messageManager.removeMessageListener("devtools:test:load", reloadCounter);
- // If we finish too early, the inspector breaks promises:
- toolbox.getPanel("inspector").once("new-root", finishUp);
- });
- });
- });
- }, toolIDs.length - 1 /* only test 1 tool in docked mode, to cut down test time */);
+ info("Start testing with the toolbox docked");
+ // Note that we actually only test 1 tool in docked mode, to cut down on test time.
+ await testOneTool(toolbox, toolIDs[toolIDs.length - 1]);
+
+ info("Switch to undocked mode");
+ await toolbox.switchHost(Toolbox.HostType.WINDOW);
+ toolbox.win.focus();
+
+ info("Now test with the toolbox undocked");
+ for (const toolID of toolIDs) {
+ await testOneTool(toolbox, toolID);
+ }
+
+ info("Switch back to docked mode");
+ await toolbox.switchHost(Toolbox.HostType.BOTTOM);
+
+ gBrowser.selectedBrowser.messageManager.removeMessageListener("devtools:test:load", reloadCounter);
+
+ await toolbox.destroy();
+ gBrowser.removeCurrentTab();
+});
+
+async function testOneTool(toolbox, toolID) {
+ info(`Select tool ${toolID}`);
+ await toolbox.selectTool(toolID);
+
+ await testReload("toolbox.reload.key", toolbox, toolID);
+ await testReload("toolbox.reload2.key", toolbox, toolID);
+ await testReload("toolbox.forceReload.key", toolbox, toolID);
+ await testReload("toolbox.forceReload2.key", toolbox, toolID);
}
-function testAllTheTools(docked, callback, toolNum = 0) {
- if (toolNum >= toolIDs.length) {
- return callback();
- }
- toolbox.selectTool(toolIDs[toolNum]).then(() => {
- testReload("toolbox.reload.key", docked, toolIDs[toolNum], () => {
- testReload("toolbox.reload2.key", docked, toolIDs[toolNum], () => {
- testReload("toolbox.forceReload.key", docked, toolIDs[toolNum], () => {
- testReload("toolbox.forceReload2.key", docked, toolIDs[toolNum], () => {
- testAllTheTools(docked, callback, toolNum + 1);
- });
- });
- });
- });
+function testReload(shortcut, toolbox, toolID) {
+ info(`Reload with ${shortcut}`);
+
+ const mm = gBrowser.selectedBrowser.messageManager;
+
+ return new Promise(resolve => {
+ // The inspector needs some special care.
+ const toolUpdated = toolID === "inspector"
+ ? toolbox.getPanel("inspector").once("new-root")
+ : Promise.resolve();
+
+ const complete = () => {
+ mm.removeMessageListener("devtools:test:load", complete);
+ toolUpdated.then(resolve);
+ };
+ mm.addMessageListener("devtools:test:load", complete);
+
+ toolbox.win.focus();
+ synthesizeKeyShortcut(L10N.getStr(shortcut), toolbox.win);
+ reloadsSent++;
});
}
-
-function testReload(shortcut, docked, toolID, callback) {
- const complete = () => {
- gBrowser.selectedBrowser.messageManager.removeMessageListener("devtools:test:load", complete);
- return callback();
- };
- gBrowser.selectedBrowser.messageManager.addMessageListener("devtools:test:load", complete);
-
- description = docked + " devtools with tool " + toolID + ", shortcut #" + shortcut;
- info("Testing reload in " + description);
- toolbox.win.focus();
- synthesizeKeyShortcut(L10N.getStr(shortcut), toolbox.win);
- reloadsSent++;
-}
-
-function finishUp() {
- toolbox.destroy().then(() => {
- gBrowser.removeCurrentTab();
-
- target = toolbox = description = reloadsSent = toolIDs = null;
-
- finish();
- });
-}
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/metrics/browser_metrics_inspector.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ head.js
+ !/devtools/client/shared/test/shared-head.js
+ !/devtools/client/shared/test/telemetry-test-helpers.js
+
+# Each metrics tests is loaded in a separate .ini file. This way the test is executed
+# individually, without any other test being executed before or after.
+[browser_metrics_inspector.js]
+skip-if = os != 'linux' || debug || asan # Results should be platform agnostic - only run on linux64-opt
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/metrics/browser_metrics_inspector.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from ../../../shared/test/shared-head.js */
+
+/**
+ * This test records the number of modules loaded by DevTools, as well as the total count
+ * of characters in those modules, when opening the inspector. These metrics are retrieved
+ * by perfherder via logs.
+ */
+
+const TEST_URL = "data:text/html;charset=UTF-8,<div>Inspector modules load test</div>";
+
+add_task(async function() {
+ await openNewTabAndToolbox(TEST_URL, "inspector");
+
+ const allModules = getFilteredModules("");
+ const inspectorModules = getFilteredModules("devtools/client/inspector");
+
+ const allModulesCount = allModules.length;
+ const inspectorModulesCount = inspectorModules.length;
+
+ const allModulesChars = countCharsInModules(allModules);
+ const inspectorModulesChars = countCharsInModules(inspectorModules);
+
+ const PERFHERDER_DATA = {
+ framework: {
+ name: "devtools"
+ },
+ suites: [{
+ name: "inspector-metrics",
+ value: allModulesChars,
+ subtests: [
+ {
+ name: "inspector-modules",
+ value: inspectorModulesCount
+ },
+ {
+ name: "inspector-chars",
+ value: inspectorModulesChars
+ },
+ {
+ name: "all-modules",
+ value: allModulesCount
+ },
+ {
+ name: "all-chars",
+ value: allModulesChars
+ },
+ ],
+ }]
+ };
+ info("PERFHERDER_DATA: " + JSON.stringify(PERFHERDER_DATA));
+
+ // Simply check that we found valid values.
+ ok(allModulesCount > inspectorModulesCount &&
+ inspectorModulesCount > 0, "Successfully recorded module count for Inspector");
+ ok(allModulesChars > inspectorModulesChars &&
+ inspectorModulesChars > 0, "Successfully recorded char count for Inspector");
+});
+
+function getFilteredModules(filter) {
+ const modules = Object.keys(loader.provider.loader.modules);
+ return modules.filter(url => url.includes(filter));
+}
+
+function countCharsInModules(modules) {
+ return modules.reduce((sum, uri) => {
+ try {
+ return sum + require("raw!" + uri).length;
+ } catch (e) {
+ // Ignore failures
+ return sum;
+ }
+ }, 0);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/metrics/head.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* import-globals-from ../../../shared/test/shared-head.js */
+/* import-globals-from ../../../shared/test/telemetry-test-helpers.js */
+
+// shared-head.js handles imports, constants, and utility functions
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", this);
+
+// So that PERFHERDER data can be extracted from the logs.
+SimpleTest.requestCompleteLog();
--- a/devtools/client/framework/toolbox-highlighter-utils.js
+++ b/devtools/client/framework/toolbox-highlighter-utils.js
@@ -23,48 +23,35 @@ const flags = require("devtools/shared/f
* This should be done once only by the toolbox itself and stored there so that
* panels can get it from there. That's because the API returned has a stateful
* scope that would be different for another instance returned by this function.
*
* @param {Toolbox} toolbox
* @return {Object} the highlighterUtils public API
*/
exports.getHighlighterUtils = function(toolbox) {
- if (!toolbox || !toolbox.target) {
+ if (!toolbox) {
throw new Error("Missing or invalid toolbox passed to getHighlighterUtils");
}
// Exported API properties will go here
const exported = {};
- // The current toolbox target
- let target = toolbox.target;
-
// Is the highlighter currently in pick mode
let isPicking = false;
// Is the box model already displayed, used to prevent dispatching
// unnecessary requests, especially during toolbox shutdown
let isNodeFrontHighlighted = false;
/**
* Release this utils, nullifying the references to the toolbox
*/
exported.release = function() {
- toolbox = target = null;
- };
-
- /**
- * Does the target have the highlighter actor.
- * The devtools must be backwards compatible with at least B2G 1.3 (28),
- * which doesn't have the highlighter actor. This can be removed as soon as
- * the minimal supported version becomes 1.4 (29)
- */
- const isRemoteHighlightable = exported.isRemoteHighlightable = function() {
- return target.client.traits.highlightable;
+ toolbox = null;
};
/**
* Make a function that initializes the inspector before it runs.
* Since the init of the inspector is asynchronous, the return value will be
* produced by Task.async and the argument should be a generator
* @param {Function*} generator A generator function
* @return {Function} A function
@@ -110,59 +97,44 @@ exports.getHighlighterUtils = function(t
return;
}
isPicking = true;
toolbox.pickerButton.isChecked = true;
await toolbox.selectTool("inspector", "inspect_dom");
toolbox.on("select", cancelPicker);
- if (isRemoteHighlightable()) {
- toolbox.walker.on("picker-node-hovered", onPickerNodeHovered);
- toolbox.walker.on("picker-node-picked", onPickerNodePicked);
- toolbox.walker.on("picker-node-previewed", onPickerNodePreviewed);
- toolbox.walker.on("picker-node-canceled", onPickerNodeCanceled);
+ toolbox.walker.on("picker-node-hovered", onPickerNodeHovered);
+ toolbox.walker.on("picker-node-picked", onPickerNodePicked);
+ toolbox.walker.on("picker-node-previewed", onPickerNodePreviewed);
+ toolbox.walker.on("picker-node-canceled", onPickerNodeCanceled);
- await toolbox.highlighter.pick(doFocus);
- toolbox.emit("picker-started");
- } else {
- // If the target doesn't have the highlighter actor, we can use the
- // walker's pick method instead, knowing that it only responds when a node
- // is picked (instead of emitting events)
- toolbox.emit("picker-started");
- const node = await toolbox.walker.pick();
- onPickerNodePicked({node: node});
- }
+ await toolbox.highlighter.pick(doFocus);
+ toolbox.emit("picker-started");
});
/**
* Stop the element picker. Note that the picker is automatically stopped when
* an element is picked
* @return A promise that resolves when the picker has stopped or immediately
* if it is already stopped
*/
const stopPicker = exported.stopPicker = requireInspector(async function() {
if (!isPicking) {
return;
}
isPicking = false;
toolbox.pickerButton.isChecked = false;
- if (isRemoteHighlightable()) {
- await toolbox.highlighter.cancelPick();
- toolbox.walker.off("picker-node-hovered", onPickerNodeHovered);
- toolbox.walker.off("picker-node-picked", onPickerNodePicked);
- toolbox.walker.off("picker-node-previewed", onPickerNodePreviewed);
- toolbox.walker.off("picker-node-canceled", onPickerNodeCanceled);
- } else {
- // If the target doesn't have the highlighter actor, use the walker's
- // cancelPick method instead
- await toolbox.walker.cancelPick();
- }
+ await toolbox.highlighter.cancelPick();
+ toolbox.walker.off("picker-node-hovered", onPickerNodeHovered);
+ toolbox.walker.off("picker-node-picked", onPickerNodePicked);
+ toolbox.walker.off("picker-node-previewed", onPickerNodePreviewed);
+ toolbox.walker.off("picker-node-canceled", onPickerNodeCanceled);
toolbox.off("select", cancelPicker);
toolbox.emit("picker-stopped");
});
/**
* Stop the picker, but also emit an event that the picker was canceled.
*/
@@ -216,23 +188,17 @@ exports.getHighlighterUtils = function(t
*/
const highlightNodeFront = exported.highlightNodeFront = requireInspector(
async function(nodeFront, options = {}) {
if (!nodeFront) {
return;
}
isNodeFrontHighlighted = true;
- if (isRemoteHighlightable()) {
- await toolbox.highlighter.showBoxModel(nodeFront, options);
- } else {
- // If the target doesn't have the highlighter actor, revert to the
- // walker's highlight method, which draws a simple outline
- await toolbox.walker.highlight(nodeFront);
- }
+ await toolbox.highlighter.showBoxModel(nodeFront, options);
toolbox.emit("node-highlight", nodeFront);
});
/**
* This is a convenience method in case you don't have a nodeFront but a
* valueGrip. This is often the case with VariablesView properties.
* This method will simply translate the grip into a nodeFront and call
@@ -266,20 +232,17 @@ exports.getHighlighterUtils = function(t
* in the markup view doesn't hide/show the highlighter to ease testing. The
* highlighter stays visible at all times, except when the mouse leaves the
* markup view, which is when this param is passed to true
* @return a promise that resolves when the highlighter is hidden
*/
exported.unhighlight = async function(forceHide = false) {
forceHide = forceHide || !flags.testing;
- // Note that if isRemoteHighlightable is true, there's no need to hide the
- // highlighter as the walker uses setTimeout to hide it after some time
- if (isNodeFrontHighlighted && forceHide && toolbox.highlighter &&
- isRemoteHighlightable()) {
+ if (isNodeFrontHighlighted && forceHide && toolbox.highlighter) {
isNodeFrontHighlighted = false;
await toolbox.highlighter.hideBoxModel();
}
// unhighlight is called when destroying the toolbox, which means that by
// now, the toolbox reference might have been nullified already.
if (toolbox) {
toolbox.emit("node-unhighlight");
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -2340,17 +2340,17 @@ Toolbox.prototype = {
* Highlight a frame in the page
*/
onHighlightFrame: async function(frameId) {
// Need to initInspector to check presence of getNodeActorFromWindowID
// and use the highlighter later
await this.initInspector();
// Only enable frame highlighting when the top level document is targeted
- if (this._supportsFrameHighlight && this.rootFrameSelected) {
+ if (this.rootFrameSelected) {
const frameActor = await this.walker.getNodeActorFromWindowID(frameId);
this.highlighterUtils.highlightNodeFront(frameActor);
}
},
/**
* A handler for 'frameUpdate' packets received from the backend.
* Following properties might be set on the packet:
@@ -2675,28 +2675,21 @@ Toolbox.prototype = {
// TODO: replace with getFront once inspector is separated from the toolbox
this._inspector = this.target.getInspector();
const pref = "devtools.inspector.showAllAnonymousContent";
const showAllAnonymousContent = Services.prefs.getBoolPref(pref);
this._walker = await this._inspector.getWalker({ showAllAnonymousContent });
this._selection = new Selection(this._walker);
this._selection.on("new-node-front", this._onNewSelectedNodeFront);
- if (this.highlighterUtils.isRemoteHighlightable()) {
- this.walker.on("highlighter-ready", this._highlighterReady);
- this.walker.on("highlighter-hide", this._highlighterHidden);
-
- const autohide = !flags.testing;
- this._highlighter = await this._inspector.getHighlighter(autohide);
- }
- if (!("_supportsFrameHighlight" in this)) {
- // Only works with FF58+ targets
- this._supportsFrameHighlight =
- await this.target.actorHasMethod("domwalker", "getNodeActorFromWindowID");
- }
+ this.walker.on("highlighter-ready", this._highlighterReady);
+ this.walker.on("highlighter-hide", this._highlighterHidden);
+
+ const autohide = !flags.testing;
+ this._highlighter = await this._inspector.getHighlighter(autohide);
}.bind(this))();
}
return this._initInspector;
},
_onNewSelectedNodeFront: function() {
// Emit a "selection-changed" event when the toolbox.selection has been set
// to a new node (or cleared). Currently used in the WebExtensions APIs (to
@@ -2764,24 +2757,16 @@ Toolbox.prototype = {
} else {
await this.highlighterUtils.stopPicker();
}
// Temporary fix for bug #1493131 - inspector has a different life cycle
// than most other fronts because it is closely related to the toolbox.
this._inspector.destroy();
if (this._highlighter) {
- // Note that if the toolbox is closed, this will work fine, but will fail
- // in case the browser is closed and will trigger a noSuchActor message.
- // We ignore the promise that |_hideBoxModel| returns, since we should still
- // proceed with the rest of destruction if it fails.
- // FF42+ now does the cleanup from the actor.
- if (!this.highlighter.traits.autoHideOnDestroy) {
- this.highlighterUtils.unhighlight();
- }
await this._highlighter.destroy();
}
if (this._selection) {
this._selection.off("new-node-front", this._onNewSelectedNodeFront);
this._selection.destroy();
}
if (this.walker) {
--- a/devtools/client/inspector/animation/animation.js
+++ b/devtools/client/inspector/animation/animation.js
@@ -1,15 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const { AnimationsFront } = require("devtools/shared/fronts/animation");
const { createElement, createFactory } = require("devtools/client/shared/vendor/react");
const { Provider } = require("devtools/client/shared/vendor/react-redux");
const EventEmitter = require("devtools/shared/event-emitter");
const App = createFactory(require("./components/App"));
const CurrentTimeTimer = require("./current-time-timer");
@@ -97,17 +96,17 @@ class AnimationInspector {
setSelectedNode,
simulateAnimation,
simulateAnimationForKeyframesProgressBar,
toggleElementPicker,
} = this;
const target = this.inspector.target;
const direction = this.win.document.dir;
- this.animationsFront = new AnimationsFront(target.client, target.form);
+ this.animationsFront = target.getFront("animations");
this.animationsFront.setWalkerActor(this.inspector.walker);
this.animationsCurrentTimeListeners = [];
this.isCurrentTimeSet = false;
const provider = createElement(Provider,
{
id: "animationinspector",
@@ -321,17 +320,22 @@ class AnimationInspector {
animation.off("changed", this.onAnimationStateChanged);
}
}
// Update existing other animations as well since the currentTime would be proceeded
// sice the scrubber position is related the currentTime.
// Also, don't update the state of removed animations since React components
// may refer to the same instance still.
- animations = await this.updateAnimations(animations);
+ try {
+ animations = await this.updateAnimations(animations);
+ } catch (_) {
+ console.error(`Updating Animations failed`);
+ return;
+ }
this.updateState(animations.concat(addedAnimations));
}
onElementPickerStarted() {
this.inspector.store.dispatch(updateElementPickerEnabled(true));
}
--- a/devtools/client/inspector/animation/test/browser_animation_logic_mutations_fast.js
+++ b/devtools/client/inspector/animation/test/browser_animation_logic_mutations_fast.js
@@ -5,18 +5,28 @@
// Test whether the animation inspector will not crash when remove/add animations faster.
add_task(async function() {
const tab = await addTab(URL_ROOT + "doc_mutations_fast.html");
const { inspector } = await openAnimationInspector();
info("Check state of the animation inspector after fast mutations");
+ const animationsFinished = waitForAnimations(inspector);
await startFastMutations(tab);
ok(inspector.panelWin.document.getElementById("animation-container"),
"Animation inspector should be live");
+ await animationsFinished;
});
async function startFastMutations(tab) {
await ContentTask.spawn(tab.linkedBrowser, {}, async function() {
await content.wrappedJSObject.startFastMutations();
});
}
+
+function waitForAnimations(inspector) {
+ // wait at least once
+ let count = 1;
+ // queue any further waits
+ inspector.animationinspector.animationsFront.on("mutations", () => count++);
+ return waitForDispatch(inspector, "UPDATE_ANIMATIONS", () => count);
+}
--- a/devtools/client/inspector/animation/test/head.js
+++ b/devtools/client/inspector/animation/test/head.js
@@ -549,16 +549,61 @@ const setStyles = async function(animati
const waitForRendering = async function(animationInspector) {
await Promise.all([
waitForAllAnimationTargets(animationInspector),
waitForAllSummaryGraph(animationInspector),
waitForAnimationDetail(animationInspector),
]);
};
+// Wait until an action of `type` is dispatched. If it's part of an
+// async operation, wait until the `status` field is "done" or "error"
+function _afterDispatchDone(store, type) {
+ return new Promise(resolve => {
+ store.dispatch({
+ // Normally we would use `services.WAIT_UNTIL`, but use the
+ // internal name here so tests aren't forced to always pass it
+ // in
+ type: "@@service/waitUntil",
+ predicate: action => {
+ if (action.type === type) {
+ return true;
+ }
+ return false;
+ },
+ run: (dispatch, getState, action) => {
+ resolve(action);
+ }
+ });
+ });
+}
+
+/**
+ * Wait for a specific action type to be dispatch.
+ * If an async action, will wait for it to be done.
+ * This is a custom waitForDispatch, and rather than having a number to wait on
+ * the function has a callback, that returns a number. This allows us to wait for
+ * an unknown number of dispatches.
+ *
+ * @memberof mochitest/waits
+ * @param {Object} inspector
+ * @param {String} type
+ * @param {Function} repeat
+ * @return {Promise}
+ * @static
+ */
+async function waitForDispatch(inspector, type, repeat) {
+ let count = 0;
+
+ while (count < repeat()) {
+ await _afterDispatchDone(inspector.store, type);
+ count++;
+ }
+}
+
/**
* Wait for rendering of animation keyframes.
*
* @param {AnimationInspector} inspector
*/
const waitForAnimationDetail = async function(animationInspector) {
if (animationInspector.state.selectedAnimation &&
--- a/devtools/client/inspector/flexbox/test/browser.ini
+++ b/devtools/client/inspector/flexbox/test/browser.ini
@@ -1,23 +1,26 @@
[DEFAULT]
tags = devtools
subsuite = devtools
support-files =
doc_flexbox_simple.html
doc_flexbox_pseudos.html
+ doc_flexbox_text_nodes.html
head.js
!/devtools/client/inspector/test/head.js
!/devtools/client/inspector/test/shared-head.js
!/devtools/client/shared/test/shared-head.js
!/devtools/client/shared/test/telemetry-test-helpers.js
!/devtools/client/shared/test/test-actor.js
!/devtools/client/shared/test/test-actor-registry.js
[browser_flexbox_highlighter_color_picker_on_ESC.js]
[browser_flexbox_highlighter_color_picker_on_RETURN.js]
[browser_flexbox_item_outline_exists.js]
[browser_flexbox_item_outline_has_correct_layout.js]
[browser_flexbox_item_outline_rotates_for_column.js]
[browser_flexbox_pseudo_elements_are_listed.js]
[browser_flexbox_sizing_info_exists.js]
[browser_flexbox_sizing_info_for_pseudos.js]
+[browser_flexbox_sizing_info_for_text_nodes.js]
[browser_flexbox_sizing_info_has_correct_sections.js]
+[browser_flexbox_text_nodes_are_listed.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_sizing_info_for_text_nodes.js
@@ -0,0 +1,38 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the flex item sizing UI also appears for text nodes.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_text_nodes.html";
+
+add_task(async function() {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ info("Select the first text node in the flex container");
+ const containerNode = await getNodeFront(".container", inspector);
+ const { nodes } = await inspector.walker.children(containerNode);
+ const firstTextNode = nodes[0];
+
+ const onFlexItemSizingRendered = waitForDOM(doc, "ul.flex-item-sizing");
+ const onFlexItemOutlineRendered = waitForDOM(doc, ".flex-outline-container");
+ await selectNode(firstTextNode, inspector);
+ const [flexSizingContainer] = await onFlexItemSizingRendered;
+ const [flexOutlineContainer] = await onFlexItemOutlineRendered;
+
+ ok(flexSizingContainer, "The flex sizing exists in the DOM");
+ ok(flexOutlineContainer, "The flex outline exists in the DOM");
+
+ info("Check that the various sizing sections are displayed");
+ const allSections = [...flexSizingContainer.querySelectorAll(".section")];
+ ok(allSections.length, "Sizing sections are displayed");
+
+ info("Check that the various parts of the outline are displayed");
+ const [basis, final] = [...flexOutlineContainer.querySelectorAll(
+ ".flex-outline-basis, .flex-outline-final")];
+ ok(basis && final, "The final and basis parts of the outline exist");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/browser_flexbox_text_nodes_are_listed.js
@@ -0,0 +1,27 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that text nodes that are flex items do appear in the list of items.
+
+const TEST_URI = URL_ROOT + "doc_flexbox_text_nodes.html";
+
+add_task(async function() {
+ await addTab(TEST_URI);
+ const { inspector, flexboxInspector } = await openLayoutView();
+ const { document: doc } = flexboxInspector;
+
+ // Select the flex container in the inspector.
+ const onItemsListRendered = waitForDOM(doc,
+ "#layout-flexbox-container .flex-item-list");
+ await selectNode(".container", inspector);
+ const [flexItemList] = await onItemsListRendered;
+
+ const items = [...flexItemList.querySelectorAll("li")];
+ is(items.length, 3, "There are 3 items displayed in the list");
+
+ is(items[0].textContent, "#text", "The first item is a text node");
+ is(items[2].textContent, "#text", "The third item is a text node");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/flexbox/test/doc_flexbox_text_nodes.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<style>
+.container {
+ width: 400px;
+ display: flex;
+}
+.container div {
+ flex-basis: 100px;
+ flex-shrink: 0;
+ background: #f06;
+ align-self: stretch;
+}
+</style>
+<div class="container">
+ A text node will be wrapped into an anonymous block container
+ <div></div>
+ Here is yet another text node
+</div>
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -92,31 +92,29 @@ const INSET_POINT_TYPES = ["top", "right
* @param {Inspector} inspector
* Inspector toolbox panel
* @param {Document} document
* The document that will contain the rule view.
* @param {Object} store
* The CSS rule view can use this object to store metadata
* that might outlast the rule view, particularly the current
* set of disabled properties.
- * @param {PageStyleFront} pageStyle
- * The PageStyleFront for communicating with the remote server.
*/
-function CssRuleView(inspector, document, store, pageStyle) {
+function CssRuleView(inspector, document, store) {
EventEmitter.decorate(this);
this.inspector = inspector;
this.cssProperties = inspector.cssProperties;
this.styleDocument = document;
this.styleWindow = this.styleDocument.defaultView;
this.store = store || {};
// References to rules marked by various editors where they intend to write changes.
// @see selectRule(), unselectRule()
this.selectedRules = new Map();
- this.pageStyle = pageStyle;
+ this.pageStyle = inspector.pageStyle;
// Allow tests to override debouncing behavior, as this can cause intermittents.
this.debounce = debounce;
this._outputParser = new OutputParser(document, this.cssProperties);
this._onAddRule = this._onAddRule.bind(this);
this._onContextMenu = this._onContextMenu.bind(this);
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -263,14 +263,13 @@ skip-if = (os == 'linux' && bits == 32 &
[browser_rules_shapes-toggle_06.js]
[browser_rules_shapes-toggle_07.js]
[browser_rules_shorthand-overridden-lists.js]
[browser_rules_strict-search-filter-computed-list_01.js]
[browser_rules_strict-search-filter_01.js]
[browser_rules_strict-search-filter_02.js]
[browser_rules_strict-search-filter_03.js]
[browser_rules_style-editor-link.js]
-skip-if = true # Bug 1309759
[browser_rules_url-click-opens-new-tab.js]
[browser_rules_urls-clickable.js]
[browser_rules_user-agent-styles.js]
[browser_rules_user-agent-styles-uneditable.js]
[browser_rules_user-property-reset.js]
--- a/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js
+++ b/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js
@@ -5,18 +5,17 @@
"use strict";
// Test the links from the rule-view to the styleeditor
const STYLESHEET_DATA_URL_CONTENTS = ["#first {",
"color: blue",
"}"].join("\n");
const STYLESHEET_DATA_URL =
- `data:text/css,${encodeURIComponent(STYLESHEET_DATA_URL_CONTENTS)}`;
-const STYLESHEET_DECODED_DATA_URL = `data:text/css,${STYLESHEET_DATA_URL_CONTENTS}`;
+ `data:text/css,${encodeURIComponent(STYLESHEET_DATA_URL_CONTENTS)}`;
const EXTERNAL_STYLESHEET_FILE_NAME = "doc_style_editor_link.css";
const EXTERNAL_STYLESHEET_URL = URL_ROOT + EXTERNAL_STYLESHEET_FILE_NAME;
const DOCUMENT_URL = "data:text/html;charset=utf-8," + encodeURIComponent(`
<html>
<head>
<title>Rule view style editor link test</title>
@@ -50,38 +49,23 @@ const DOCUMENT_URL = "data:text/html;cha
</html>
`);
add_task(async function() {
await addTab(DOCUMENT_URL);
const {toolbox, inspector, view, testActor} = await openRuleView();
await selectNode("div", inspector);
- await testInlineStyle(view);
+ testRuleViewLinkLabel(view);
await testFirstInlineStyleSheet(view, toolbox, testActor);
await testSecondInlineStyleSheet(view, toolbox, testActor);
await testExternalStyleSheet(view, toolbox, testActor);
await testDisabledStyleEditor(view, toolbox);
});
-async function testInlineStyle(view) {
- info("Testing inline style");
-
- const onTab = waitForTab();
- info("Clicking on the first link in the rule-view");
- clickLinkByIndex(view, 0);
-
- const tab = await onTab;
-
- const tabURI = tab.linkedBrowser.documentURI.spec;
- ok(tabURI.startsWith("view-source:"), "View source tab is open");
- info("Closing tab");
- gBrowser.removeTab(tab);
-}
-
async function testFirstInlineStyleSheet(view, toolbox, testActor) {
info("Testing inline stylesheet");
info("Listening for toolbox switch to the styleeditor");
const onSwitch = waitForStyleEditor(toolbox);
info("Clicking an inline stylesheet");
clickLinkByIndex(view, 4);
@@ -98,17 +82,16 @@ async function testSecondInlineStyleShee
info("Waiting for the stylesheet editor to be selected");
const panel = toolbox.getCurrentPanel();
const onSelected = panel.UI.once("editor-selected");
info("Switching back to the inspector panel in the toolbox");
await toolbox.selectTool("inspector");
info("Clicking on second inline stylesheet link");
- testRuleViewLinkLabel(view);
clickLinkByIndex(view, 3);
const editor = await onSelected;
is(toolbox.currentToolId, "styleeditor",
"The style editor is selected again");
await validateStyleEditorSheet(editor, 1, testActor);
}
@@ -118,35 +101,34 @@ async function testExternalStyleSheet(vi
info("Waiting for the stylesheet editor to be selected");
const panel = toolbox.getCurrentPanel();
const onSelected = panel.UI.once("editor-selected");
info("Switching back to the inspector panel in the toolbox");
await toolbox.selectTool("inspector");
info("Clicking on an external stylesheet link");
- testRuleViewLinkLabel(view);
clickLinkByIndex(view, 1);
const editor = await onSelected;
is(toolbox.currentToolId, "styleeditor",
"The style editor is selected again");
await validateStyleEditorSheet(editor, 2, testActor);
}
async function validateStyleEditorSheet(editor, expectedSheetIndex, testActor) {
info("validating style editor stylesheet");
is(editor.styleSheet.styleSheetIndex, expectedSheetIndex,
"loaded stylesheet index matches document stylesheet");
const href = editor.styleSheet.href || editor.styleSheet.nodeHref;
const expectedHref = await testActor.eval(
- `content.document.styleSheets[${expectedSheetIndex}].href ||
- content.document.location.href`);
+ `document.styleSheets[${expectedSheetIndex}].href ||
+ document.location.href`);
is(href, expectedHref, "loaded stylesheet href matches document stylesheet");
}
async function testDisabledStyleEditor(view, toolbox) {
info("Testing with the style editor disabled");
info("Switching to the inspector panel in the toolbox");
@@ -178,19 +160,19 @@ async function testDisabledStyleEditor(v
function testRuleViewLinkLabel(view) {
info("Checking the data URL link label");
let link = getRuleViewLinkByIndex(view, 1);
let labelElem = link.querySelector(".ruleview-rule-source-label");
let value = labelElem.textContent;
let tooltipText = labelElem.getAttribute("title");
- is(value, `${STYLESHEET_DATA_URL_CONTENTS}:1`,
+ is(value, encodeURIComponent(STYLESHEET_DATA_URL_CONTENTS) + ":1",
"Rule view data URL stylesheet display value matches contents");
- is(tooltipText, `${STYLESHEET_DECODED_DATA_URL}:1`,
+ is(tooltipText, STYLESHEET_DATA_URL + ":1",
"Rule view data URL stylesheet tooltip text matches the full URI path");
info("Checking the external link label");
link = getRuleViewLinkByIndex(view, 2);
labelElem = link.querySelector(".ruleview-rule-source-label");
value = labelElem.textContent;
tooltipText = labelElem.getAttribute("title");
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -278,17 +278,22 @@ RuleEditor.prototype = {
if (this.source.hasAttribute("unselectable") || !this._currentLocation) {
return;
}
const target = this.ruleView.inspector.target;
if (Tools.styleEditor.isTargetSupported(target)) {
gDevTools.showToolbox(target, "styleeditor").then(toolbox => {
const {url, line, column} = this._currentLocation;
- toolbox.getCurrentPanel().selectStyleSheet(url, line, column);
+
+ if (!this.rule.sheet.href && this.rule.sheet.nodeHref) {
+ toolbox.getCurrentPanel().selectStyleSheet(this.rule.sheet, line, column);
+ } else {
+ toolbox.getCurrentPanel().selectStyleSheet(url, line, column);
+ }
});
}
},
/**
* Update the text of the source link to reflect whether we're showing
* original sources or not. This is a callback for
* SourceMapURLService.subscribe, which see.
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -353,13 +353,9 @@ pref("devtools.aboutdebugging.collapsibi
// about:debugging: only show system add-ons in local builds by default.
#ifdef MOZILLA_OFFICIAL
pref("devtools.aboutdebugging.showSystemAddons", false);
#else
pref("devtools.aboutdebugging.showSystemAddons", true);
#endif
// Map top-level await expressions in the console
-#if defined(NIGHTLY_BUILD)
pref("devtools.debugger.features.map-await-expression", true);
-#else
-pref("devtools.debugger.features.map-await-expression", false);
-#endif
--- a/devtools/client/shared/test/browser_dbg_WorkerTargetActor.attach.js
+++ b/devtools/client/shared/test/browser_dbg_WorkerTargetActor.attach.js
@@ -39,40 +39,40 @@ function test() {
// If a page still has pending network requests, it will not be moved into
// the bfcache. Consequently, we cannot use waitForWorkerListChanged here,
// because the worker is not guaranteed to have finished loading when it is
// registered. Instead, we have to wait for the promise returned by
// createWorker in the tab to be resolved.
yield createWorkerInTab(tab, WORKER1_URL);
let { workers } = yield listWorkers(targetFront);
- let [, workerClient1] = yield attachWorker(targetFront,
+ let [, workerTargetFront1] = yield attachWorker(targetFront,
findWorker(workers, WORKER1_URL));
- is(workerClient1.isClosed, false, "worker in tab 1 should not be closed");
+ is(workerTargetFront1.isClosed, false, "worker in tab 1 should not be closed");
executeSoon(() => {
BrowserTestUtils.loadURI(tab.linkedBrowser, TAB2_URL);
});
- yield waitForWorkerClose(workerClient1);
- is(workerClient1.isClosed, true, "worker in tab 1 should be closed");
+ yield waitForWorkerClose(workerTargetFront1);
+ is(workerTargetFront1.isClosed, true, "worker in tab 1 should be closed");
yield createWorkerInTab(tab, WORKER2_URL);
({ workers } = yield listWorkers(targetFront));
- const [, workerClient2] = yield attachWorker(targetFront,
+ const [, workerTargetFront2] = yield attachWorker(targetFront,
findWorker(workers, WORKER2_URL));
- is(workerClient2.isClosed, false, "worker in tab 2 should not be closed");
+ is(workerTargetFront2.isClosed, false, "worker in tab 2 should not be closed");
executeSoon(() => {
tab.linkedBrowser.goBack();
});
- yield waitForWorkerClose(workerClient2);
- is(workerClient2.isClosed, true, "worker in tab 2 should be closed");
+ yield waitForWorkerClose(workerTargetFront2);
+ is(workerTargetFront2.isClosed, true, "worker in tab 2 should be closed");
({ workers } = yield listWorkers(targetFront));
- [, workerClient1] = yield attachWorker(targetFront,
+ [, workerTargetFront1] = yield attachWorker(targetFront,
findWorker(workers, WORKER1_URL));
- is(workerClient1.isClosed, false, "worker in tab 1 should not be closed");
+ is(workerTargetFront1.isClosed, false, "worker in tab 1 should not be closed");
yield close(client);
SpecialPowers.setIntPref(MAX_TOTAL_VIEWERS, oldMaxTotalViewers);
finish();
});
}
--- a/devtools/client/shared/test/browser_dbg_worker-console-01.js
+++ b/devtools/client/shared/test/browser_dbg_worker-console-01.js
@@ -13,22 +13,22 @@
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/helper_workers.js",
this);
var TAB_URL = EXAMPLE_URL + "doc_WorkerTargetActor.attachThread-tab.html";
var WORKER_URL = "code_WorkerTargetActor.attachThread-worker.js";
add_task(async function testNormalExecution() {
- const {client, tab, workerClient, toolbox} =
+ const {client, tab, workerTargetFront, toolbox} =
await initWorkerDebugger(TAB_URL, WORKER_URL);
const jsterm = await getSplitConsole(toolbox);
const executed = await jsterm.execute("this.location.toString()");
ok(executed.textContent.includes(WORKER_URL),
"Evaluating the global's location works");
terminateWorkerInTab(tab, WORKER_URL);
- await waitForWorkerClose(workerClient);
- await gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
+ await waitForWorkerClose(workerTargetFront);
+ await gDevTools.closeToolbox(TargetFactory.forWorker(workerTargetFront));
await close(client);
await removeTab(tab);
});
--- a/devtools/client/shared/test/browser_dbg_worker-console-02.js
+++ b/devtools/client/shared/test/browser_dbg_worker-console-02.js
@@ -14,17 +14,17 @@ Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/devtools/client/shared/test/helper_workers.js",
this);
var TAB_URL = EXAMPLE_URL + "doc_WorkerTargetActor.attachThread-tab.html";
var WORKER_URL = "code_WorkerTargetActor.attachThread-worker.js";
add_task(async function testWhilePaused() {
const dbg = await initWorkerDebugger(TAB_URL, WORKER_URL);
- const {client, tab, workerClient, toolbox} = dbg;
+ const {client, tab, workerTargetFront, toolbox} = dbg;
// Execute some basic math to make sure evaluations are working.
const jsterm = await getSplitConsole(toolbox);
let executed = await jsterm.execute("10000+1");
ok(executed.textContent.includes("10001"), "Text for message appeared correct");
await clickElement(dbg, "pause");
once(dbg.client, "willInterrupt").then(() => {
@@ -50,13 +50,13 @@ add_task(async function testWhilePaused(
info("Trying to get the result of command3");
executed = await command3;
ok(executed.textContent.includes("ReferenceError: foobar is not defined"),
"command3 executed successfully");
await resume(dbg);
terminateWorkerInTab(tab, WORKER_URL);
- await waitForWorkerClose(workerClient);
- await gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
+ await waitForWorkerClose(workerTargetFront);
+ await gDevTools.closeToolbox(TargetFactory.forWorker(workerTargetFront));
await close(client);
await removeTab(tab);
});
--- a/devtools/client/shared/test/browser_dbg_worker-console-03.js
+++ b/devtools/client/shared/test/browser_dbg_worker-console-03.js
@@ -15,17 +15,17 @@ Services.scriptloader.loadSubScript(
this);
var TAB_URL = EXAMPLE_URL + "doc_WorkerTargetActor.attachThread-tab.html";
var WORKER_URL = "code_WorkerTargetActor.attachThread-worker.js";
// Test to see if creating the pause from the console works.
add_task(async function testPausedByConsole() {
const dbg = await initWorkerDebugger(TAB_URL, WORKER_URL);
- const {client, tab, workerClient, toolbox} = dbg;
+ const {client, tab, workerTargetFront, toolbox} = dbg;
const jsterm = await getSplitConsole(toolbox);
let executed = await jsterm.execute("10000+1");
ok(executed.textContent.includes("10001"),
"Text for message appeared correct");
await clickElement(dbg, "pause");
@@ -41,13 +41,13 @@ add_task(async function testPausedByCons
info("Waiting for a resume");
await clickElement(dbg, "resume");
executed = await pausedExecution;
ok(executed.textContent.includes("10002"),
"Text for message appeared correct");
terminateWorkerInTab(tab, WORKER_URL);
- await waitForWorkerClose(workerClient);
- await gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
+ await waitForWorkerClose(workerTargetFront);
+ await gDevTools.closeToolbox(TargetFactory.forWorker(workerTargetFront));
await close(client);
await removeTab(tab);
});
--- a/devtools/client/shared/test/browser_dbg_worker-console-04.js
+++ b/devtools/client/shared/test/browser_dbg_worker-console-04.js
@@ -22,28 +22,28 @@ Services.scriptloader.loadSubScript(
// See bug 1018184 for resolving these issues.
const { PromiseTestUtils } = scopedCuImport("resource://testing-common/PromiseTestUtils.jsm");
PromiseTestUtils.whitelistRejectionsGlobally(/connection just closed/);
const TAB_URL = EXAMPLE_URL + "doc_WorkerTargetActor.attachThread-tab.html";
const WORKER_URL = "code_WorkerTargetActor.attachThread-worker.js";
add_task(async function testPausedByConsole() {
- const {client, tab, workerClient, toolbox} =
+ const {client, tab, workerTargetFront, toolbox} =
await initWorkerDebugger(TAB_URL, WORKER_URL);
info("Check Date objects can be used in the console");
const jsterm = await getSplitConsole(toolbox);
let executed = await jsterm.execute("new Date(0)");
ok(executed.textContent.includes("1970-01-01T00:00:00.000Z"),
"Text for message appeared correct");
info("Check RegExp objects can be used in the console");
executed = await jsterm.execute("new RegExp('.*')");
ok(executed.textContent.includes("/.*/"),
"Text for message appeared correct");
terminateWorkerInTab(tab, WORKER_URL);
- await waitForWorkerClose(workerClient);
- await gDevTools.closeToolbox(TargetFactory.forWorker(workerClient));
+ await waitForWorkerClose(workerTargetFront);
+ await gDevTools.closeToolbox(TargetFactory.forWorker(workerTargetFront));
await close(client);
await removeTab(tab);
});
--- a/devtools/client/shared/test/helper_workers.js
+++ b/devtools/client/shared/test/helper_workers.js
@@ -113,24 +113,24 @@ function findWorker(workers, url) {
return null;
}
function attachWorker(targetFront, worker) {
info("Attaching to worker with url '" + worker.url + "'.");
return targetFront.attachWorker(worker.actor);
}
-function attachThread(workerClient, options) {
+function attachThread(workerTargetFront, options) {
info("Attaching to thread.");
- return workerClient.attachThread(options);
+ return workerTargetFront.attachThread(options);
}
-async function waitForWorkerClose(workerClient) {
+async function waitForWorkerClose(workerTargetFront) {
info("Waiting for worker to close.");
- await workerClient.once("close");
+ await workerTargetFront.once("close");
info("Worker did close.");
}
// Return a promise with a reference to jsterm, opening the split
// console if necessary. This cleans up the split console pref so
// it won't pollute other tests.
function getSplitConsole(toolbox, win) {
if (!win) {
@@ -159,30 +159,30 @@ async function initWorkerDebugger(TAB_UR
const tab = await addTab(TAB_URL);
const { tabs } = await listTabs(client);
const [, targetFront] = await attachTarget(client, findTab(tabs, TAB_URL));
await createWorkerInTab(tab, WORKER_URL);
const { workers } = await listWorkers(targetFront);
- const [, workerClient] = await attachWorker(targetFront,
+ const [, workerTargetFront] = await attachWorker(targetFront,
findWorker(workers, WORKER_URL));
- const toolbox = await gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
+ const toolbox = await gDevTools.showToolbox(TargetFactory.forWorker(workerTargetFront),
"jsdebugger",
Toolbox.HostType.WINDOW);
const debuggerPanel = toolbox.getCurrentPanel();
const gDebugger = debuggerPanel.panelWin;
const context = createDebuggerContext(toolbox);
- return { ...context, client, tab, targetFront, workerClient, toolbox, gDebugger};
+ return { ...context, client, tab, targetFront, workerTargetFront, toolbox, gDebugger};
}
// Override addTab/removeTab as defined by shared-head, since these have
// an extra window parameter and add a frame script
this.addTab = function addTab(url, win) {
info("Adding tab: " + url);
const deferred = getDeferredPromise().defer();
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -545,16 +545,17 @@ class JSTerm extends Component {
const inspectorSelection = this.hud.owner.getInspectorSelection();
if (inspectorSelection && inspectorSelection.nodeFront) {
selectedNodeActor = inspectorSelection.nodeFront.actorID;
}
const { ConsoleCommand } = require("devtools/client/webconsole/types");
const cmdMessage = new ConsoleCommand({
messageText: executeString,
+ timeStamp: Date.now(),
});
this.hud.proxy.dispatchMessageAdd(cmdMessage);
let mappedExpressionRes = null;
try {
mappedExpressionRes = await this.hud.owner.getMappedExpression(executeString);
} catch (e) {
console.warn("Error when calling getMappedExpression", e);
--- a/devtools/client/webconsole/components/message-types/ConsoleCommand.js
+++ b/devtools/client/webconsole/components/message-types/ConsoleCommand.js
@@ -30,26 +30,28 @@ function ConsoleCommand(props) {
} = props;
const {
indent,
source,
type,
level,
messageText,
+ timeStamp,
} = message;
// This uses a Custom Element to syntax highlight when possible. If it's not
// (no CodeMirror editor), then it will just render text.
const messageBody = createElement("syntax-highlighted", null, messageText);
return Message({
source,
type,
level,
topLevelClasses: [],
messageBody,
serviceContainer,
indent,
+ timeStamp,
timestampsVisible,
});
}
module.exports = ConsoleCommand;
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -200,16 +200,17 @@ skip-if = verify
[browser_jsterm_autocomplete_inside_text.js]
[browser_jsterm_autocomplete_native_getters.js]
[browser_jsterm_autocomplete_nav_and_tab_key.js]
[browser_jsterm_autocomplete_paste_undo.js]
[browser_jsterm_autocomplete_return_key_no_selection.js]
[browser_jsterm_autocomplete_return_key.js]
[browser_jsterm_autocomplete_width.js]
[browser_jsterm_autocomplete-properties-with-non-alphanumeric-names.js]
+[browser_jsterm_await_concurrent.js]
[browser_jsterm_await_error.js]
[browser_jsterm_await_helper_dollar_underscore.js]
[browser_jsterm_await_paused.js]
[browser_jsterm_await.js]
[browser_jsterm_completion_bracket_cached_results.js]
[browser_jsterm_completion_bracket.js]
[browser_jsterm_completion_case_sensitivity.js]
[browser_jsterm_completion.js]
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_await.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_await.js
@@ -1,12 +1,12 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
-// Test that top-level await expression work as expected.
+// Test that top-level await expressions work as expected.
"use strict";
const TEST_URI = "data:text/html;charset=utf-8,Web Console test top-level await";
add_task(async function() {
// Enable await mapping.
await pushPref("devtools.debugger.features.map-await-expression", true);
@@ -16,70 +16,55 @@ add_task(async function() {
await performTests();
// And then run it with the CodeMirror-powered one.
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
- const {jsterm} = hud;
const executeAndWaitForResultMessage = (input, expectedOutput) =>
executeAndWaitForMessage(hud, input, expectedOutput, ".result");
info("Evaluate a top-level await expression");
const simpleAwait = `await new Promise(r => setTimeout(() => r(["await1"]), 500))`;
await executeAndWaitForResultMessage(
simpleAwait,
`Array [ "await1" ]`,
);
// Check that the resulting promise of the async iife is not displayed.
- let messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
- let messagesText = Array.from(messages).map(n => n.textContent).join(" - ");
+ const messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
+ const messagesText = Array.from(messages).map(n => n.textContent).join(" - ");
is(messagesText, `${simpleAwait} - Array [ "await1" ]`,
"The output contains the the expected messages");
+ // Check that the timestamp of the result is accurate
+ const {
+ visibleMessages,
+ messagesById
+ } = hud.ui.consoleOutput.getStore().getState().messages;
+ const [commandId, resultId] = visibleMessages;
+ const delta = messagesById.get(resultId).timeStamp -
+ messagesById.get(commandId).timeStamp;
+ ok(delta >= 500,
+ `The result has a timestamp at least 500ms (${delta}ms) older than the command`);
+
info("Check that assigning the result of a top-level await expression works");
await executeAndWaitForResultMessage(
`x = await new Promise(r => setTimeout(() => r("await2"), 500))`,
`await2`,
);
let message = await executeAndWaitForResultMessage(
`"-" + x + "-"`,
`"-await2-"`,
);
ok(message.node, "`x` was assigned as expected");
- info("Check that concurrent await expression work fine");
- hud.ui.clearOutput();
- const delays = [1000, 500, 2000, 1500];
- const inputs = delays.map(delay => `await new Promise(
- r => setTimeout(() => r("await-concurrent-" + ${delay}), ${delay}))`);
-
- // Let's wait for the message that sould be displayed last.
- const onMessage = waitForMessage(hud, "await-concurrent-2000", ".message.result");
- for (const input of inputs) {
- jsterm.execute(input);
- }
- await onMessage;
-
- messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
- messagesText = Array.from(messages).map(n => n.textContent);
- const expectedMessages = [
- ...inputs,
- `"await-concurrent-500"`,
- `"await-concurrent-1000"`,
- `"await-concurrent-1500"`,
- `"await-concurrent-2000"`,
- ];
- is(JSON.stringify(messagesText, null, 2), JSON.stringify(expectedMessages, null, 2),
- "The output contains the the expected messages, in the expected order");
-
info("Check that a logged promise is still displayed as a promise");
message = await executeAndWaitForResultMessage(
`new Promise(r => setTimeout(() => r(1), 1000))`,
`Promise {`,
);
ok(message, "Promise are displayed as expected");
}
copy from devtools/client/webconsole/test/mochitest/browser_jsterm_await.js
copy to devtools/client/webconsole/test/mochitest/browser_jsterm_await_concurrent.js
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_await.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_await_concurrent.js
@@ -1,12 +1,12 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
-// Test that top-level await expression work as expected.
+// Test that multiple concurrent top-level await expressions work as expected.
"use strict";
const TEST_URI = "data:text/html;charset=utf-8,Web Console test top-level await";
add_task(async function() {
// Enable await mapping.
await pushPref("devtools.debugger.features.map-await-expression", true);
@@ -18,68 +18,32 @@ add_task(async function() {
await pushPref("devtools.webconsole.jsterm.codeMirror", true);
await performTests();
});
async function performTests() {
const hud = await openNewTabAndConsole(TEST_URI);
const {jsterm} = hud;
- const executeAndWaitForResultMessage = (input, expectedOutput) =>
- executeAndWaitForMessage(hud, input, expectedOutput, ".result");
-
- info("Evaluate a top-level await expression");
- const simpleAwait = `await new Promise(r => setTimeout(() => r(["await1"]), 500))`;
- await executeAndWaitForResultMessage(
- simpleAwait,
- `Array [ "await1" ]`,
- );
-
- // Check that the resulting promise of the async iife is not displayed.
- let messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
- let messagesText = Array.from(messages).map(n => n.textContent).join(" - ");
- is(messagesText, `${simpleAwait} - Array [ "await1" ]`,
- "The output contains the the expected messages");
-
- info("Check that assigning the result of a top-level await expression works");
- await executeAndWaitForResultMessage(
- `x = await new Promise(r => setTimeout(() => r("await2"), 500))`,
- `await2`,
- );
-
- let message = await executeAndWaitForResultMessage(
- `"-" + x + "-"`,
- `"-await2-"`,
- );
- ok(message.node, "`x` was assigned as expected");
-
- info("Check that concurrent await expression work fine");
hud.ui.clearOutput();
- const delays = [1000, 500, 2000, 1500];
+ const delays = [3000, 500, 9000, 6000];
const inputs = delays.map(delay => `await new Promise(
r => setTimeout(() => r("await-concurrent-" + ${delay}), ${delay}))`);
// Let's wait for the message that sould be displayed last.
- const onMessage = waitForMessage(hud, "await-concurrent-2000", ".message.result");
+ const onMessage = waitForMessage(hud, "await-concurrent-9000", ".message.result");
for (const input of inputs) {
jsterm.execute(input);
}
await onMessage;
- messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
- messagesText = Array.from(messages).map(n => n.textContent);
+ const messages = hud.ui.outputNode.querySelectorAll(".message .message-body");
+ const messagesText = Array.from(messages).map(n => n.textContent);
const expectedMessages = [
...inputs,
`"await-concurrent-500"`,
- `"await-concurrent-1000"`,
- `"await-concurrent-1500"`,
- `"await-concurrent-2000"`,
+ `"await-concurrent-3000"`,
+ `"await-concurrent-6000"`,
+ `"await-concurrent-9000"`,
];
is(JSON.stringify(messagesText, null, 2), JSON.stringify(expectedMessages, null, 2),
"The output contains the the expected messages, in the expected order");
-
- info("Check that a logged promise is still displayed as a promise");
- message = await executeAndWaitForResultMessage(
- `new Promise(r => setTimeout(() => r(1), 1000))`,
- `Promise {`,
- );
- ok(message, "Promise are displayed as expected");
}
--- a/devtools/client/webconsole/types.js
+++ b/devtools/client/webconsole/types.js
@@ -17,16 +17,17 @@ exports.ConsoleCommand = function(props)
allowRepeating: false,
messageText: null,
source: MESSAGE_SOURCE.JAVASCRIPT,
type: MESSAGE_TYPE.COMMAND,
level: MESSAGE_LEVEL.LOG,
groupId: null,
indent: 0,
private: false,
+ timeStamp: null,
}, props);
};
exports.ConsoleMessage = function(props) {
return Object.assign({
id: null,
allowRepeating: true,
source: null,
--- a/devtools/client/webconsole/webconsole-output-wrapper.js
+++ b/devtools/client/webconsole/webconsole-output-wrapper.js
@@ -61,17 +61,17 @@ WebConsoleOutputWrapper.prototype = {
timeStamp,
}]));
},
hudProxy: hud.proxy,
openLink: (url, e) => {
hud.owner.openLink(url, e);
},
canRewind: () => {
- if (!hud.owner.target.activeTab) {
+ if (!(hud.owner && hud.owner.target && hud.owner.target.activeTab)) {
return false;
}
return hud.owner.target.activeTab.traits.canRewind;
},
createElement: nodename => {
return this.document.createElement(nodename);
},
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -227,18 +227,16 @@ var UI = {
cancelBusyTimeout: function() {
clearTimeout(this._busyTimeout);
},
busyWithProgressUntil: function(promise, operationDescription) {
const busy = this.busyUntil(promise, operationDescription);
const win = document.querySelector("window");
- const progress = document.querySelector("#action-busy-determined");
- progress.mode = "undetermined";
win.classList.add("busy-determined");
win.classList.remove("busy-undetermined");
return busy;
},
busyUntil: function(promise, operationDescription) {
// Freeze the UI until the promise is resolved. A timeout will unfreeze the
// UI, just in case the promise never gets resolved.
--- a/devtools/client/webide/content/webide.xul
+++ b/devtools/client/webide/content/webide.xul
@@ -119,17 +119,17 @@
<vbox flex="1">
<hbox id="action-buttons-container" class="busy">
<toolbarbutton id="action-button-play" class="action-button" command="cmd_play" tooltiptext="&projectMenu_play_label;"/>
<toolbarbutton id="action-button-stop" class="action-button" command="cmd_stop" tooltiptext="&projectMenu_stop_label;"/>
<toolbarbutton id="action-button-debug" class="action-button" command="cmd_toggleToolbox" tooltiptext="&projectMenu_debug_label;"/>
<hbox id="action-busy" align="center">
<html:img id="action-busy-undetermined" src="chrome://webide/skin/throbber.svg"/>
- <progressmeter id="action-busy-determined"/>
+ <html:progress id="action-busy-determined"/>
</hbox>
</hbox>
<hbox id="panel-buttons-container">
<spacer flex="1"/>
<toolbarbutton id="runtime-panel-button" class="panel-button">
<image class="panel-button-image"/>
<label class="panel-button-label" value="&runtimeButton_label;"/>
--- a/devtools/server/actors/highlighters.js
+++ b/devtools/server/actors/highlighters.js
@@ -108,19 +108,16 @@ exports.HighlighterActor = protocol.Acto
get conn() {
return this._inspector && this._inspector.conn;
},
form: function() {
return {
actor: this.actorID,
- traits: {
- autoHideOnDestroy: true
- }
};
},
_createHighlighter: function() {
this._isPreviousWindowXUL = isXUL(this._targetActor.window);
if (!this._isPreviousWindowXUL) {
this._highlighter = new BoxModelHighlighter(this._highlighterEnv,
--- a/devtools/server/actors/layout.js
+++ b/devtools/server/actors/layout.js
@@ -310,33 +310,39 @@ const LayoutActor = ActorClassWithSpec(l
if (node.rawNode) {
node = node.rawNode;
}
const treeWalker = this.walker.getDocumentWalker(node, SHOW_ELEMENT);
let currentNode = treeWalker.currentNode;
let displayType = this.walker.getNode(currentNode).displayType;
- if (!displayType) {
- return null;
- }
-
- if (type == "flex") {
- if (displayType == "inline-flex" || displayType == "flex") {
- return new FlexboxActor(this, currentNode);
- } else if (onlyLookAtCurrentNode) {
+ // If the node is an element, check first if it is itself a flex or a grid.
+ if (currentNode.nodeType === currentNode.ELEMENT_NODE) {
+ if (!displayType) {
return null;
}
- } else if (type == "grid" &&
- (displayType == "inline-grid" || displayType == "grid")) {
- return new GridActor(this, currentNode);
+
+ if (type == "flex") {
+ if (displayType == "inline-flex" || displayType == "flex") {
+ return new FlexboxActor(this, currentNode);
+ } else if (onlyLookAtCurrentNode) {
+ return null;
+ }
+ } else if (type == "grid" &&
+ (displayType == "inline-grid" || displayType == "grid")) {
+ return new GridActor(this, currentNode);
+ }
}
// Otherwise, check if this is a flex/grid item or the parent node is a flex/grid
// container.
+ // Note that text nodes that are children of flex/grid containers are wrapped in
+ // anonymous containers, so even if their displayType getter returns null we still
+ // want to walk up the chain to find their container.
while ((currentNode = treeWalker.parentNode())) {
if (!currentNode) {
break;
}
displayType = this.walker.getNode(currentNode).displayType;
if (type == "flex" &&
--- a/devtools/server/actors/replay/debugger.js
+++ b/devtools/server/actors/replay/debugger.js
@@ -57,17 +57,21 @@ ReplayDebugger.prototype = {
/////////////////////////////////////////////////////////
replaying: true,
canRewind: RecordReplayControl.canRewind,
replayResumeBackward() { RecordReplayControl.resume(/* forward = */ false); },
replayResumeForward() { RecordReplayControl.resume(/* forward = */ true); },
replayTimeWarp: RecordReplayControl.timeWarp,
- replayPause: RecordReplayControl.pause,
+
+ replayPause() {
+ RecordReplayControl.pause();
+ this._repaint();
+ },
addDebuggee() {},
removeAllDebuggees() {},
replayingContent(url) {
return this._sendRequest({ type: "getContent", url });
},
@@ -85,16 +89,28 @@ ReplayDebugger.prototype = {
// diverge from the recording. In such cases we want to be interacting with a
// replaying process (if there is one), as recording child processes won't
// provide useful responses to such requests.
_sendRequestAllowDiverge(request) {
RecordReplayControl.maybeSwitchToReplayingChild();
return this._sendRequest(request);
},
+ // Update graphics according to the current state of the child process. This
+ // should be done anytime we pause and allow the user to interact with the
+ // debugger.
+ _repaint() {
+ const rv = this._sendRequestAllowDiverge({ type: "repaint" });
+ if ("width" in rv && "height" in rv) {
+ RecordReplayControl.hadRepaint(rv.width, rv.height);
+ } else {
+ RecordReplayControl.hadRepaintFailure();
+ }
+ },
+
_setBreakpoint(handler, position, data) {
const id = RecordReplayControl.setBreakpoint(handler, position);
this._breakpoints.push({id, position, data});
},
_clearMatchingBreakpoints(callback) {
this._breakpoints = this._breakpoints.filter(breakpoint => {
if (callback(breakpoint)) {
@@ -158,20 +174,34 @@ ReplayDebugger.prototype = {
_addScript(data) {
if (!this._scripts[data.id]) {
this._scripts[data.id] = new ReplayDebuggerScript(this, data);
}
return this._scripts[data.id];
},
- findScripts() {
- // Note: Debugger's findScripts() method takes a query argument, which
- // we ignore here.
- const data = this._sendRequest({ type: "findScripts" });
+ _convertScriptQuery(query) {
+ // Make a copy of the query, converting properties referring to debugger
+ // things into their associated ids.
+ const rv = Object.assign({}, query);
+ if ("global" in query) {
+ rv.global = query.global._data.id;
+ }
+ if ("source" in query) {
+ rv.source = query.source._data.id;
+ }
+ return rv;
+ },
+
+ findScripts(query) {
+ const data = this._sendRequest({
+ type: "findScripts",
+ query: this._convertScriptQuery(query)
+ });
return data.map(script => this._addScript(script));
},
findAllConsoleMessages() {
const messages = this._sendRequest({ type: "findConsoleMessages" });
return messages.map(this._convertConsoleMessage.bind(this));
},
@@ -310,42 +340,45 @@ ReplayDebugger.prototype = {
set onNewScript(handler) {
this._breakpointKindSetter("NewScript", handler,
() => handler.call(this, this._getNewScript()));
},
get onEnterFrame() { return this._breakpointKindGetter("EnterFrame"); },
set onEnterFrame(handler) {
this._breakpointKindSetter("EnterFrame", handler,
- () => handler.call(this, this.getNewestFrame()));
+ () => { this._repaint();
+ handler.call(this, this.getNewestFrame()); });
},
get replayingOnPopFrame() {
return this._searchBreakpoints(({position, data}) => {
return (position.kind == "OnPop" && !position.script) ? data : null;
});
},
set replayingOnPopFrame(handler) {
if (handler) {
- this._setBreakpoint(() => handler.call(this, this.getNewestFrame()),
+ this._setBreakpoint(() => { this._repaint();
+ handler.call(this, this.getNewestFrame()); },
{ kind: "OnPop" }, handler);
} else {
this._clearMatchingBreakpoints(({position}) => {
return position.kind == "OnPop" && !position.script;
});
}
},
get replayingOnForcedPause() {
return this._breakpointKindGetter("ForcedPause");
},
set replayingOnForcedPause(handler) {
this._breakpointKindSetter("ForcedPause", handler,
- () => handler.call(this, this.getNewestFrame()));
+ () => { this._repaint();
+ handler.call(this, this.getNewestFrame()); });
},
getNewConsoleMessage() {
const message = this._sendRequest({ type: "getNewConsoleMessage" });
return this._convertConsoleMessage(message);
},
get onConsoleMessage() {
@@ -383,17 +416,18 @@ ReplayDebuggerScript.prototype = {
},
getLineOffsets(line) { return this._forward("getLineOffsets", line); },
getOffsetLocation(pc) { return this._forward("getOffsetLocation", pc); },
getSuccessorOffsets(pc) { return this._forward("getSuccessorOffsets", pc); },
getPredecessorOffsets(pc) { return this._forward("getPredecessorOffsets", pc); },
setBreakpoint(offset, handler) {
- this._dbg._setBreakpoint(() => { handler.hit(this._dbg.getNewestFrame()); },
+ this._dbg._setBreakpoint(() => { this._dbg._repaint();
+ handler.hit(this._dbg.getNewestFrame()); },
{ kind: "Break", script: this._data.id, offset },
handler);
},
clearBreakpoint(handler) {
this._dbg._clearMatchingBreakpoints(({position, data}) => {
return position.script == this._data.id && handler == data;
});
@@ -500,17 +534,18 @@ ReplayDebuggerFrame.prototype = {
({position}) => this._positionMatches(position, "OnStep")
);
},
setReplayingOnStep(handler, offsets) {
this._clearOnStepBreakpoints();
offsets.forEach(offset => {
this._dbg._setBreakpoint(
- () => handler.call(this._dbg.getNewestFrame()),
+ () => { this._dbg._repaint();
+ handler.call(this._dbg.getNewestFrame()); },
{ kind: "OnStep",
script: this._data.script,
offset,
frameIndex: this._data.index },
handler);
});
},
@@ -518,16 +553,17 @@ ReplayDebuggerFrame.prototype = {
return this._dbg._searchBreakpoints(({position, data}) => {
return this._positionMatches(position, "OnPop") ? data : null;
});
},
set onPop(handler) {
if (handler) {
this._dbg._setBreakpoint(() => {
+ this._dbg._repaint();
const result = this._dbg._sendRequest({ type: "popFrameResult" });
handler.call(this._dbg.getNewestFrame(),
this._dbg._convertCompletionValue(result));
},
{ kind: "OnPop", script: this._data.script, frameIndex: this._data.index },
handler);
} else {
this._dbg._clearMatchingBreakpoints(
@@ -599,41 +635,45 @@ ReplayDebuggerObject.prototype = {
getOwnPropertySymbols() {
// Symbol properties are not handled yet.
return [];
},
getOwnPropertyDescriptor(name) {
this._ensureProperties();
- return this._properties[name];
+ const desc = this._properties[name];
+ return desc ? this._convertPropertyDescriptor(desc) : null;
},
_ensureProperties() {
if (!this._properties) {
const properties = this._dbg._sendRequestAllowDiverge({
type: "getObjectProperties",
id: this._data.id
});
this._properties = {};
- properties.forEach(({name, desc}) => {
- if ("value" in desc) {
- desc.value = this._dbg._convertValue(desc.value);
- }
- if ("get" in desc) {
- desc.get = this._dbg._getObject(desc.get);
- }
- if ("set" in desc) {
- desc.set = this._dbg._getObject(desc.set);
- }
- this._properties[name] = desc;
- });
+ properties.forEach(({name, desc}) => { this._properties[name] = desc; });
}
},
+ _convertPropertyDescriptor(desc) {
+ const rv = Object.assign({}, desc);
+ if ("value" in desc) {
+ rv.value = this._dbg._convertValue(desc.value);
+ }
+ if ("get" in desc) {
+ rv.get = this._dbg._getObject(desc.get);
+ }
+ if ("set" in desc) {
+ rv.set = this._dbg._getObject(desc.set);
+ }
+ return rv;
+ },
+
get allocationSite() { NYI(); },
get errorMessageName() { NYI(); },
get errorNotes() { NYI(); },
get errorLineNumber() { NYI(); },
get errorColumnNumber() { NYI(); },
get proxyTarget() { NYI(); },
get proxyHandler() { NYI(); },
get isPromise() { NYI(); },
--- a/devtools/server/actors/replay/graphics.js
+++ b/devtools/server/actors/replay/graphics.js
@@ -9,17 +9,17 @@
// which are connected to the compositor in the UI process in the usual way.
// We need to update the contents of the document to draw the raw graphics data
// provided by the child process.
"use strict";
ChromeUtils.import("resource://gre/modules/Services.jsm");
-function updateWindow(window, buffer, width, height) {
+function updateWindow(window, buffer, width, height, hadFailure) {
// Make sure the window has a canvas filling the screen.
let canvas = window.middlemanCanvas;
if (!canvas) {
canvas = window.document.createElement("canvas");
window.document.body.style.margin = "0px";
window.document.body.insertBefore(canvas, window.document.body.firstChild);
window.middlemanCanvas = canvas;
}
@@ -31,33 +31,43 @@ function updateWindow(window, buffer, wi
// been scaled in the child process. To avoid scaling the graphics twice,
// transform the canvas to undo the scaling.
const scale = window.devicePixelRatio;
if (scale != 1) {
canvas.style.transform =
`scale(${ 1 / scale }) translate(-${ width / scale }px, -${ height / scale }px)`;
}
+ const cx = canvas.getContext("2d");
+
const graphicsData = new Uint8Array(buffer);
- const imageData = canvas.getContext("2d").getImageData(0, 0, width, height);
+ const imageData = cx.getImageData(0, 0, width, height);
imageData.data.set(graphicsData);
- canvas.getContext("2d").putImageData(imageData, 0, 0);
+ cx.putImageData(imageData, 0, 0);
+
+ // Indicate to the user when repainting failed and we are showing old painted
+ // graphics instead of the most up-to-date graphics.
+ if (hadFailure) {
+ cx.fillStyle = "red";
+ cx.font = "48px serif";
+ cx.fillText("PAINT FAILURE", 10, 50);
+ }
// Make recording/replaying tabs easier to differentiate from other tabs.
window.document.title = "RECORD/REPLAY";
}
// Entry point for when we have some new graphics data from the child process
// to draw.
// eslint-disable-next-line no-unused-vars
-function Update(buffer, width, height) {
+function Update(buffer, width, height, hadFailure) {
try {
// Paint to all windows we can find. Hopefully there is only one.
for (const window of Services.ww.getWindowEnumerator()) {
- updateWindow(window, buffer, width, height);
+ updateWindow(window, buffer, width, height, hadFailure);
}
} catch (e) {
dump("Middleman Graphics Update Exception: " + e + "\n");
}
}
// eslint-disable-next-line no-unused-vars
var EXPORTED_SYMBOLS = [
--- a/devtools/server/actors/replay/replay.js
+++ b/devtools/server/actors/replay/replay.js
@@ -518,20 +518,40 @@ function forwardToScript(name) {
}
///////////////////////////////////////////////////////////////////////////////
// Handlers
///////////////////////////////////////////////////////////////////////////////
const gRequestHandlers = {
+ repaint() {
+ if (!RecordReplayControl.maybeDivergeFromRecording()) {
+ return {};
+ }
+ return RecordReplayControl.repaint();
+ },
+
findScripts(request) {
+ const query = Object.assign({}, request.query);
+ if ("global" in query) {
+ query.global = gPausedObjects.getObject(query.global);
+ }
+ if ("source" in query) {
+ query.source = gScriptSources.getObject(query.source);
+ if (!query.source) {
+ return [];
+ }
+ }
+ const scripts = dbg.findScripts(query);
const rv = [];
- gScripts.forEach((id) => {
- rv.push(getScriptData(id));
+ scripts.forEach(script => {
+ if (considerScript(script)) {
+ rv.push(getScriptData(gScripts.getId(script)));
+ }
});
return rv;
},
getScript(request) {
return getScriptData(request.id);
},
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -111,19 +111,16 @@ function RootActor(connection, parameter
}
RootActor.prototype = {
constructor: RootActor,
applicationType: "browser",
traits: {
sources: true,
- // Whether the server-side highlighter actor exists and can be used to
- // remotely highlight nodes (see server/actors/highlighters.js)
- highlightable: true,
networkMonitor: true,
// Whether the storage inspector actor to inspect cookies, etc.
storageInspector: true,
// Whether storage inspector is read only
storageInspectorReadOnly: true,
// Whether conditional breakpoints are supported
conditionalBreakpoints: true,
// Whether the server supports full source actors (breakpoints on
--- a/devtools/server/actors/targets/addon.js
+++ b/devtools/server/actors/targets/addon.js
@@ -91,17 +91,16 @@ AddonTargetActor.prototype = {
debuggable: this._addon.isDebuggable,
temporarilyInstalled: this._addon.temporarilyInstalled,
type: this._addon.type,
isWebExtension: this._addon.isWebExtension,
isAPIExtension: this._addon.isAPIExtension,
consoleActor: this._consoleActor.actorID,
traits: {
- highlightable: false,
networkMonitor: false,
},
};
},
destroy() {
this.conn.removeActorPool(this._contextPool);
this._contextPool = null;
--- a/devtools/server/actors/targets/content-process.js
+++ b/devtools/server/actors/targets/content-process.js
@@ -114,17 +114,16 @@ ContentProcessTargetActor.prototype = {
name: "Content process",
consoleActor: this._consoleActor.actorID,
chromeDebugger: this.threadActor.actorID,
memoryActor: this.memoryActor.actorID,
promisesActor: this._promisesActor.actorID,
traits: {
- highlightable: false,
networkMonitor: false,
},
};
},
onListWorkers: function() {
if (!this._workerList) {
this._workerList = new WorkerTargetActorList(this.conn, {});
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -971,21 +971,24 @@ WebConsoleActor.prototype =
* `resultID` field, and potentially a promise in the `helperResult` or in the
* `awaitResult` field.
*
* @return object
* The response packet to send to with the unique id in the
* `resultID` field, with a sanitized helperResult field.
*/
_waitForResultAndSend: async function(response) {
+ let updateTimestamp = false;
+
// Wait for asynchronous command completion before sending back the response
if (
response.helperResult && typeof response.helperResult.then == "function"
) {
response.helperResult = await response.helperResult;
+ updateTimestamp = true;
} else if (response.awaitResult && typeof response.awaitResult.then === "function") {
let result;
try {
result = await response.awaitResult;
} catch (e) {
// The promise was rejected. We let the engine handle this as it will report a
// `uncaught exception` error.
response.topLevelAwaitRejected = true;
@@ -995,16 +998,24 @@ WebConsoleActor.prototype =
// `createValueGrip` expect a debuggee value, while here we have the raw object.
// We need to call `makeDebuggeeValue` on it to make it work.
const dbgResult = this.makeDebuggeeValue(result);
response.result = this.createValueGrip(dbgResult);
}
// Remove the promise from the response object.
delete response.awaitResult;
+
+ updateTimestamp = true;
+ }
+
+ if (updateTimestamp) {
+ // We need to compute the timestamp again as the one in the response was set before
+ // doing the evaluation, which is now innacurate since we waited for a bit.
+ response.timestamp = Date.now();
}
// Finally, send an unsolicited evaluationResult packet with
// the normal return value
this.conn.sendActorEvent(this.actorID, "evaluationResult", response);
},
/**
--- a/devtools/shared/client/thread-client.js
+++ b/devtools/shared/client/thread-client.js
@@ -20,17 +20,17 @@ loader.lazyRequireGetter(this, "SourceCl
const noop = () => {};
/**
* Creates a thread client for the remote debugging protocol server. This client
* is a front to the thread actor created in the server side, hiding the
* protocol details in a traditional JavaScript API.
*
- * @param client DebuggerClient, WorkerClient or BrowsingContextFront
+ * @param client DebuggerClient, WorkerTargetFront or BrowsingContextFront
* The parent of the thread (tab for target-scoped debuggers,
* DebuggerClient for chrome debuggers).
* @param actor string
* The actor ID for this thread.
*/
function ThreadClient(client, actor) {
this._parent = client;
this.client = client instanceof DebuggerClient ? client : client.client;
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -1599,17 +1599,23 @@ var generateRequestMethods = function(ac
* @param object proto
* The object prototype. Must have a 'typeName' property,
* should have method definitions, can have event definitions.
*/
var FrontClassWithSpec = function(actorSpec, frontProto) {
// Existing Fronts are relying on the initialize instead of constructor methods.
const cls = function() {
const instance = Object.create(cls.prototype);
- instance.initialize.apply(instance, arguments);
+ const initializer = instance.initialize.apply(instance, arguments);
+
+ // Async Initialization
+ // return a promise that resolves with the instance if the initializer is async
+ if (initializer && typeof initializer.then === "function") {
+ return initializer.then(resolve => instance);
+ }
return instance;
};
cls.prototype = extend(Front.prototype, generateRequestMethods(actorSpec, frontProto));
if (!registeredTypes.has(actorSpec.typeName)) {
types.addActorType(actorSpec.typeName);
}
registeredTypes.get(actorSpec.typeName).frontClass = cls;
--- a/devtools/shared/webconsole/test/common.js
+++ b/devtools/shared/webconsole/test/common.js
@@ -106,26 +106,26 @@ var _attachConsole = async function(
const { workers } = await targetFront.listWorkers();
const workerTargetActor = workers.filter(w => w.url == workerName)[0].actor;
if (!workerTargetActor) {
console.error("listWorkers failed. Unable to find the " +
"worker actor\n");
return;
}
- const [workerResponse, workerClient] =
+ const [workerResponse, workerTargetFront] =
await targetFront.attachWorker(workerTargetActor);
- if (!workerClient || workerResponse.error) {
- console.error("attachWorker failed. No worker client or " +
+ if (!workerTargetFront || workerResponse.error) {
+ console.error("attachWorker failed. No worker target front or " +
" error: " + workerResponse.error);
return;
}
- await workerClient.attachThread({});
- state.actor = workerClient.consoleActor;
- state.dbgClient.attachConsole(workerClient.consoleActor, listeners)
+ await workerTargetFront.attachThread({});
+ state.actor = workerTargetFront.consoleActor;
+ state.dbgClient.attachConsole(workerTargetFront.consoleActor, listeners)
.then(_onAttachConsole.bind(null, state), _onAttachError.bind(null, state));
} else {
state.actor = tab.consoleActor;
state.dbgClient.attachConsole(tab.consoleActor, listeners)
.then(_onAttachConsole.bind(null, state), _onAttachError.bind(null, state));
}
};
--- a/dom/animation/test/style/test_interpolation-from-interpolatematrix-to-none.html
+++ b/dom/animation/test/style/test_interpolation-from-interpolatematrix-to-none.html
@@ -32,12 +32,12 @@ test(function(t) {
Math.sin(Math.PI / 8) + ',' +
-Math.sin(Math.PI / 8) + ',' +
Math.cos(Math.PI / 8) + ',' +
'25, 0)';
assert_matrix_equals(getComputedStyle(target).transform,
interpolated_matrix,
'the expected matrix from interpolatematrix(' +
'translateX(100px), rotate(90deg), 0.5) to none at 50%');
-}, 'Test interpolation from interpolatmatrix to none at 50%');
+}, 'Test interpolation from interpolatematrix to none at 50%');
</script>
</html>
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -253,16 +253,17 @@
#include "mozilla/ServoStyleSet.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/dom/SVGDocument.h"
#include "mozilla/dom/SVGSVGElement.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/TabGroup.h"
#ifdef MOZ_XUL
+#include "mozilla/dom/XULBroadcastManager.h"
#include "mozilla/dom/TreeBoxObject.h"
#include "nsIXULWindow.h"
#include "nsXULCommandDispatcher.h"
#include "nsXULPopupManager.h"
#include "nsIDocShellTreeOwner.h"
#endif
#include "nsIPresShellInlines.h"
@@ -1740,16 +1741,20 @@ nsDocument::~nsDocument()
// Could be null here if Init() failed or if we have been unlinked.
mCSSLoader->DropDocumentReference();
}
if (mStyleImageLoader) {
mStyleImageLoader->DropDocumentReference();
}
+ if (mXULBroadcastManager) {
+ mXULBroadcastManager->DropDocumentReference();
+ }
+
delete mHeaderData;
ClearAllBoxObjects();
mPendingTitleChangeEvent.Revoke();
mPlugins.Clear();
}
@@ -5108,16 +5113,19 @@ nsDocument::EndUpdate()
--mUpdateNestLevel;
// This set of updates may have created XBL bindings. Let the
// binding manager know we're done.
MaybeEndOutermostXBLUpdate();
MaybeInitializeFinalizeFrameLoaders();
+ if (mXULBroadcastManager) {
+ mXULBroadcastManager->MaybeBroadcast();
+ }
}
void
nsDocument::BeginLoad()
{
MOZ_ASSERT(!mDidCallBeginLoad);
mDidCallBeginLoad = true;
@@ -10242,16 +10250,25 @@ nsIDocument::GetCommandDispatcher()
}
if (!mCommandDispatcher) {
// Create our command dispatcher and hook it up.
mCommandDispatcher = new nsXULCommandDispatcher(this);
}
return mCommandDispatcher;
}
+void
+nsIDocument::InitializeXULBroadcastManager()
+{
+ if (mXULBroadcastManager) {
+ return;
+ }
+ mXULBroadcastManager = new XULBroadcastManager(this);
+}
+
static JSObject*
GetScopeObjectOfNode(nsINode* node)
{
MOZ_ASSERT(node, "Must not be called with null.");
// Window root occasionally keeps alive a node of a document whose
// window is already dead. If in this brief period someone calls
// GetPopupNode and we return that node, we can end up creating a
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -143,16 +143,17 @@ class ImageLoader;
class Rule;
} // namespace css
namespace dom {
class Animation;
class AnonymousContent;
class Attr;
class BoxObject;
+class XULBroadcastManager;
class ClientInfo;
class ClientState;
class CDATASection;
class Comment;
struct CustomElementDefinition;
class DocGroup;
class DocumentL10n;
class DocumentFragment;
@@ -3437,16 +3438,25 @@ public:
const mozilla::dom::BlockParsingOptions& aOptions,
mozilla::ErrorResult& aRv);
already_AddRefed<nsIURI> GetMozDocumentURIIfNotForErrorPages();
mozilla::dom::Promise* GetDocumentReadyForIdle(mozilla::ErrorResult& aRv);
nsIDOMXULCommandDispatcher* GetCommandDispatcher();
+ bool HasXULBroadcastManager() const
+ {
+ return mXULBroadcastManager;
+ };
+ void InitializeXULBroadcastManager();
+ mozilla::dom::XULBroadcastManager* GetXULBroadcastManager() const
+ {
+ return mXULBroadcastManager;
+ }
already_AddRefed<nsINode> GetPopupNode();
void SetPopupNode(nsINode* aNode);
nsINode* GetPopupRangeParent(ErrorResult& aRv);
int32_t GetPopupRangeOffset(ErrorResult& aRv);
already_AddRefed<nsINode> GetTooltipNode();
void SetTooltipNode(nsINode* aNode) { /* do nothing */ }
// ParentNode
@@ -4734,16 +4744,18 @@ protected:
// document.close(), and document.write() when they are invoked by the parser.
uint32_t mThrowOnDynamicMarkupInsertionCounter;
// Count of unload/beforeunload/pagehide operations in progress.
uint32_t mIgnoreOpensDuringUnloadCounter;
nsCOMPtr<nsIDOMXULCommandDispatcher> mCommandDispatcher; // [OWNER] of the focus tracker
+ RefPtr<mozilla::dom::XULBroadcastManager> mXULBroadcastManager;
+
// At the moment, trackers might be blocked by Tracking Protection or FastBlock.
// In order to know the numbers of trackers detected and blocked, we add
// these two values here and those are shared by TP and FB.
uint32_t mNumTrackersFound;
uint32_t mNumTrackersBlocked;
mozilla::EnumSet<mozilla::Telemetry::LABELS_DOCUMENT_ANALYTICS_TRACKER_FASTBLOCKED>
mTrackerBlockedReasons;
rename from dom/webidl/Flex.webidl
rename to dom/chrome-webidl/Flex.webidl
--- a/dom/webidl/Flex.webidl
+++ b/dom/chrome-webidl/Flex.webidl
@@ -4,50 +4,73 @@
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/**
* These objects support visualization of flex containers by the
* dev tools.
*/
+/**
+ * A flex container's main and cross axes are either horizontal or
+ * vertical, each with two possible directions.
+ */
+enum FlexPhysicalDirection {
+ "horizontal-lr",
+ "horizontal-rl",
+ "vertical-tb",
+ "vertical-bt",
+};
+
[ChromeOnly]
interface Flex
{
- sequence<FlexLine> getLines();
+ sequence<FlexLineValues> getLines();
+
+ /**
+ * The physical direction in which successive flex items are placed,
+ * within a flex line in this flex container.
+ */
+ readonly attribute FlexPhysicalDirection mainAxisDirection;
+
+ /**
+ * The physical direction in which successive flex lines are placed
+ * in this flex container (if it is or were multi-line).
+ */
+ readonly attribute FlexPhysicalDirection crossAxisDirection;
};
/**
* Lines with items that have been shrunk are shrinking; with items
* that have grown are growing, and all others are unchanged.
*/
enum FlexLineGrowthState { "unchanged", "shrinking", "growing" };
[ChromeOnly]
-interface FlexLine
+interface FlexLineValues
{
readonly attribute FlexLineGrowthState growthState;
readonly attribute double crossStart;
readonly attribute double crossSize;
// firstBaselineOffset measures from flex-start edge.
readonly attribute double firstBaselineOffset;
// lastBaselineOffset measures from flex-end edge.
readonly attribute double lastBaselineOffset;
/**
- * getItems() returns FlexItems only for the Elements in this Flex
- * container -- ignoring struts and abs-pos Elements.
+ * getItems() returns FlexItemValues only for the Elements in
+ * this Flex container -- ignoring struts and abs-pos Elements.
*/
- sequence<FlexItem> getItems();
+ sequence<FlexItemValues> getItems();
};
[ChromeOnly]
-interface FlexItem
+interface FlexItemValues
{
readonly attribute Node? node;
readonly attribute double mainBaseSize;
readonly attribute double mainDeltaSize;
readonly attribute double mainMinSize;
readonly attribute double mainMaxSize;
readonly attribute double crossMinSize;
readonly attribute double crossMaxSize;
--- a/dom/chrome-webidl/moz.build
+++ b/dom/chrome-webidl/moz.build
@@ -5,16 +5,19 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
with Files("**"):
BUG_COMPONENT = ("Core", "DOM")
with Files("ChannelWrapper.webidl"):
BUG_COMPONENT = ("WebExtensions", "Request Handling")
+with Files("Flex.webidl"):
+ BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
+
with Files("HeapSnapshot.webidl"):
BUG_COMPONENT = ("DevTools", "Memory")
with Files("InspectorUtils.webidl"):
BUG_COMPONENT = ("DevTools", "Inspector")
with Files("MatchGlob.webidl"):
BUG_COMPONENT = ("WebExtensions", "General")
@@ -28,16 +31,17 @@ with Files("WebExtension*.webidl"):
PREPROCESSED_WEBIDL_FILES = [
'ChromeUtils.webidl',
]
WEBIDL_FILES = [
'BrowsingContext.webidl',
'ChannelWrapper.webidl',
'DominatorTree.webidl',
+ 'Flex.webidl',
'HeapSnapshot.webidl',
'InspectorUtils.webidl',
'IteratorResult.webidl',
'MatchGlob.webidl',
'MatchPattern.webidl',
'MessageManager.webidl',
'MozDocumentObserver.webidl',
'MozSharedMap.webidl',
--- a/dom/file/MemoryBlobImpl.h
+++ b/dom/file/MemoryBlobImpl.h
@@ -120,16 +120,17 @@ public:
uint32_t aLength,
nsIInputStream** _retval);
NS_DECL_THREADSAFE_ISUPPORTS
// These are mandatory.
NS_FORWARD_NSIINPUTSTREAM(mStream->)
NS_FORWARD_NSISEEKABLESTREAM(mSeekableStream->)
+ NS_FORWARD_NSITELLABLESTREAM(mSeekableStream->)
NS_FORWARD_NSICLONEABLEINPUTSTREAM(mCloneableInputStream->)
// This is optional. We use a conditional QI to keep it from being called
// if the underlying stream doesn't support it.
NS_FORWARD_NSIIPCSERIALIZABLEINPUTSTREAM(mSerializableInputStream->)
private:
~DataOwnerAdapter() {}
--- a/dom/flex/Flex.cpp
+++ b/dom/flex/Flex.cpp
@@ -1,17 +1,17 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
#include "Flex.h"
-#include "FlexLine.h"
+#include "FlexLineValues.h"
#include "mozilla/dom/FlexBinding.h"
#include "nsFlexContainerFrame.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Flex, mParent, mLines)
NS_IMPL_CYCLE_COLLECTING_ADDREF(Flex)
@@ -32,28 +32,43 @@ Flex::Flex(Element* aParent,
// going to keep it around.
const ComputedFlexContainerInfo* containerInfo =
aFrame->GetFlexContainerInfo();
MOZ_ASSERT(containerInfo, "Should only be passed a frame with info.");
mLines.SetLength(containerInfo->mLines.Length());
uint32_t index = 0;
for (auto&& l : containerInfo->mLines) {
- FlexLine* line = new FlexLine(this, &l);
+ FlexLineValues* line = new FlexLineValues(this, &l);
mLines.ElementAt(index) = line;
index++;
}
+
+ mMainAxisDirection = containerInfo->mMainAxisDirection;
+ mCrossAxisDirection = containerInfo->mCrossAxisDirection;
}
JSObject*
Flex::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return Flex_Binding::Wrap(aCx, this, aGivenProto);
}
void
-Flex::GetLines(nsTArray<RefPtr<FlexLine>>& aResult)
+Flex::GetLines(nsTArray<RefPtr<FlexLineValues>>& aResult)
{
aResult.AppendElements(mLines);
}
+FlexPhysicalDirection
+Flex::MainAxisDirection() const
+{
+ return mMainAxisDirection;
+}
+
+FlexPhysicalDirection
+Flex::CrossAxisDirection() const
+{
+ return mCrossAxisDirection;
+}
+
} // namespace dom
} // namespace mozilla
--- a/dom/flex/Flex.h
+++ b/dom/flex/Flex.h
@@ -3,25 +3,26 @@
/* 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/. */
#ifndef mozilla_dom_Flex_h
#define mozilla_dom_Flex_h
#include "mozilla/dom/Element.h"
+#include "mozilla/dom/FlexBinding.h"
#include "nsISupports.h"
#include "nsWrapperCache.h"
class nsFlexContainerFrame;
namespace mozilla {
namespace dom {
-class FlexLine;
+class FlexLineValues;
class Flex : public nsISupports
, public nsWrapperCache
{
public:
explicit Flex(Element* aParent, nsFlexContainerFrame* aFrame);
protected:
@@ -32,19 +33,23 @@ public:
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Flex)
virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
Element* GetParentObject()
{
return mParent;
}
- void GetLines(nsTArray<RefPtr<FlexLine>>& aResult);