mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
e7165f9b1e
@ -112,16 +112,21 @@ devtools/client/webconsole/**
|
||||
!devtools/client/webconsole/console-commands.js
|
||||
devtools/client/webide/**
|
||||
!devtools/client/webide/components/webideCli.js
|
||||
devtools/server/**
|
||||
devtools/server/*.js
|
||||
devtools/server/*.jsm
|
||||
!devtools/server/child.js
|
||||
!devtools/server/css-logic.js
|
||||
!devtools/server/main.js
|
||||
devtools/server/actors/**
|
||||
!devtools/server/actors/inspector.js
|
||||
!devtools/server/actors/highlighters/eye-dropper.js
|
||||
!devtools/server/actors/webbrowser.js
|
||||
!devtools/server/actors/webextension.js
|
||||
!devtools/server/actors/styles.js
|
||||
!devtools/server/actors/string.js
|
||||
!devtools/server/actors/csscoverage.js
|
||||
devtools/server/performance/**
|
||||
devtools/server/tests/**
|
||||
devtools/shared/*.js
|
||||
!devtools/shared/css-lexer.js
|
||||
!devtools/shared/defer.js
|
||||
|
@ -1035,6 +1035,7 @@ pref("services.sync.prefs.sync.addons.ignoreUserEnabledChanges", true);
|
||||
// could weaken the pref locally, install an add-on from an untrusted
|
||||
// source, and this would propagate automatically to other,
|
||||
// uncompromised Sync-connected devices.
|
||||
pref("services.sync.prefs.sync.browser.ctrlTab.previews", true);
|
||||
pref("services.sync.prefs.sync.browser.download.useDownloadDir", true);
|
||||
pref("services.sync.prefs.sync.browser.formfill.enable", true);
|
||||
pref("services.sync.prefs.sync.browser.link.open_newwindow", true);
|
||||
|
@ -7288,50 +7288,39 @@ var gIdentityHandler = {
|
||||
}
|
||||
},
|
||||
|
||||
setPermission: function (aPermission, aState) {
|
||||
if (aState == SitePermissions.getDefault(aPermission))
|
||||
SitePermissions.remove(gBrowser.currentURI, aPermission);
|
||||
else
|
||||
SitePermissions.set(gBrowser.currentURI, aPermission, aState);
|
||||
},
|
||||
|
||||
_createPermissionItem: function (aPermission) {
|
||||
let menulist = document.createElement("menulist");
|
||||
let menupopup = document.createElement("menupopup");
|
||||
for (let state of aPermission.availableStates) {
|
||||
let menuitem = document.createElement("menuitem");
|
||||
menuitem.setAttribute("value", state.id);
|
||||
menuitem.setAttribute("label", state.label);
|
||||
menupopup.appendChild(menuitem);
|
||||
}
|
||||
menulist.appendChild(menupopup);
|
||||
menulist.setAttribute("value", aPermission.state);
|
||||
menulist.setAttribute("oncommand", "gIdentityHandler.setPermission('" +
|
||||
aPermission.id + "', this.value)");
|
||||
menulist.setAttribute("id", "identity-popup-permission:" + aPermission.id);
|
||||
|
||||
let label = document.createElement("label");
|
||||
label.setAttribute("flex", "1");
|
||||
label.setAttribute("class", "identity-popup-permission-label");
|
||||
label.setAttribute("control", menulist.getAttribute("id"));
|
||||
label.textContent = aPermission.label;
|
||||
let container = document.createElement("hbox");
|
||||
container.setAttribute("class", "identity-popup-permission-item");
|
||||
container.setAttribute("align", "center");
|
||||
|
||||
let img = document.createElement("image");
|
||||
let isBlocked = (aPermission.state == SitePermissions.BLOCK) ? " blocked" : "";
|
||||
img.setAttribute("class",
|
||||
"identity-popup-permission-icon " + aPermission.id + "-icon" + isBlocked);
|
||||
|
||||
let container = document.createElement("hbox");
|
||||
container.setAttribute("align", "center");
|
||||
container.appendChild(img);
|
||||
container.appendChild(label);
|
||||
container.appendChild(menulist);
|
||||
let nameLabel = document.createElement("label");
|
||||
nameLabel.setAttribute("flex", "1");
|
||||
nameLabel.setAttribute("class", "identity-popup-permission-label");
|
||||
nameLabel.textContent = SitePermissions.getPermissionLabel(aPermission.id);
|
||||
|
||||
// The menuitem text can be long and we don't want the dropdown
|
||||
// to expand to the width of unselected labels.
|
||||
// Need to set this attribute after it's appended, otherwise it gets
|
||||
// overridden with sizetopopup="pref".
|
||||
menulist.setAttribute("sizetopopup", "none");
|
||||
let stateLabel = document.createElement("label");
|
||||
stateLabel.setAttribute("flex", "1");
|
||||
stateLabel.setAttribute("class", "identity-popup-permission-state-label");
|
||||
stateLabel.textContent = SitePermissions.getStateLabel(
|
||||
aPermission.id, aPermission.state);
|
||||
|
||||
let button = document.createElement("button");
|
||||
button.setAttribute("class", "identity-popup-permission-remove-button");
|
||||
button.addEventListener("command", () => {
|
||||
this._permissionList.removeChild(container);
|
||||
this._identityPopupMultiView.setHeightToFit();
|
||||
SitePermissions.remove(gBrowser.currentURI, aPermission.id);
|
||||
});
|
||||
|
||||
container.appendChild(img);
|
||||
container.appendChild(nameLabel);
|
||||
container.appendChild(stateLabel);
|
||||
container.appendChild(button);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
@ -725,7 +725,7 @@
|
||||
<image id="identity-icon"
|
||||
consumeanchor="identity-box"
|
||||
onclick="PageProxyClickHandler(event);"/>
|
||||
<box id="blocked-permissions-container" align="center">
|
||||
<box id="blocked-permissions-container" align="center" tooltiptext="">
|
||||
<image data-permission-id="geo" class="notification-anchor-icon geo-icon blocked" role="button"
|
||||
aria-label="&urlbar.geolocationNotificationAnchor.label;"/>
|
||||
<image data-permission-id="desktop-notification" class="notification-anchor-icon desktop-notification-icon blocked" role="button"
|
||||
|
@ -234,7 +234,6 @@ Sanitizer.prototype = {
|
||||
let seenException;
|
||||
let yieldCounter = 0;
|
||||
let refObj = {};
|
||||
TelemetryStopwatch.start("FX_SANITIZE_COOKIES", refObj);
|
||||
|
||||
// Clear cookies.
|
||||
TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2", refObj);
|
||||
@ -287,7 +286,6 @@ Sanitizer.prototype = {
|
||||
// complete, we introduce a soft timeout. Once this timeout has
|
||||
// elapsed, we proceed with the shutdown of Firefox.
|
||||
let promiseClearPluginCookies;
|
||||
TelemetryStopwatch.start("FX_SANITIZE_PLUGINS", refObj);
|
||||
try {
|
||||
// We don't want to wait for this operation to complete...
|
||||
promiseClearPluginCookies = this.promiseClearPluginCookies(range);
|
||||
@ -306,10 +304,6 @@ Sanitizer.prototype = {
|
||||
// If this exception is raised before the soft timeout, it
|
||||
// will appear in `seenException`. Otherwise, it's too late
|
||||
// to do anything about it.
|
||||
}).then(() => {
|
||||
// Finally, update statistics.
|
||||
TelemetryStopwatch.finish("FX_SANITIZE_PLUGINS", refObj);
|
||||
TelemetryStopwatch.finish("FX_SANITIZE_COOKIES", refObj);
|
||||
});
|
||||
|
||||
if (seenException) {
|
||||
@ -361,15 +355,9 @@ Sanitizer.prototype = {
|
||||
|
||||
offlineApps: {
|
||||
clear: Task.async(function* (range) {
|
||||
let refObj = {};
|
||||
TelemetryStopwatch.start("FX_SANITIZE_OFFLINEAPPS", refObj);
|
||||
try {
|
||||
Components.utils.import("resource:///modules/offlineAppCache.jsm");
|
||||
// This doesn't wait for the cleanup to be complete.
|
||||
OfflineAppCacheHelper.clear();
|
||||
} finally {
|
||||
TelemetryStopwatch.finish("FX_SANITIZE_OFFLINEAPPS", refObj);
|
||||
}
|
||||
Components.utils.import("resource:///modules/offlineAppCache.jsm");
|
||||
// This doesn't wait for the cleanup to be complete.
|
||||
OfflineAppCacheHelper.clear();
|
||||
})
|
||||
},
|
||||
|
||||
|
@ -100,10 +100,6 @@ let gWhitelist = [{
|
||||
file: "netErrorApp.dtd",
|
||||
key: "securityOverride.warningContent",
|
||||
type: "single-quote"
|
||||
}, {
|
||||
file: "sync.properties",
|
||||
key: "client.name2",
|
||||
type: "apostrophe"
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -7,7 +7,6 @@ const PERMISSIONS_PAGE = "http://example.com/browser/browser/base/content/test/g
|
||||
var {SitePermissions} = Cu.import("resource:///modules/SitePermissions.jsm", {});
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
SitePermissions.remove(gBrowser.currentURI, "install");
|
||||
SitePermissions.remove(gBrowser.currentURI, "cookie");
|
||||
SitePermissions.remove(gBrowser.currentURI, "geo");
|
||||
SitePermissions.remove(gBrowser.currentURI, "camera");
|
||||
@ -30,27 +29,21 @@ add_task(function* testMainViewVisible() {
|
||||
ok(!is_hidden(emptyLabel), "List of permissions is empty");
|
||||
gIdentityHandler._identityPopup.hidden = true;
|
||||
|
||||
gIdentityHandler.setPermission("install", SitePermissions.ALLOW);
|
||||
SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.ALLOW);
|
||||
|
||||
gIdentityHandler._identityBox.click();
|
||||
ok(is_hidden(emptyLabel), "List of permissions is not empty");
|
||||
|
||||
let labelText = SitePermissions.getPermissionLabel("install");
|
||||
let labelText = SitePermissions.getPermissionLabel("camera");
|
||||
let labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
|
||||
is(labels.length, 1, "One permission visible in main view");
|
||||
is(labels[0].textContent, labelText, "Correct value");
|
||||
|
||||
let menulists = permissionsList.querySelectorAll("menulist");
|
||||
is(menulists.length, 1, "One permission visible in main view");
|
||||
is(menulists[0].id, "identity-popup-permission:install", "Install permission visible");
|
||||
is(menulists[0].value, "1", "Correct value on install menulist");
|
||||
gIdentityHandler._identityPopup.hidden = true;
|
||||
|
||||
let img = menulists[0].parentNode.querySelector("image");
|
||||
let img = permissionsList.querySelector("image.identity-popup-permission-icon");
|
||||
ok(img, "There is an image for the permissions");
|
||||
ok(img.classList.contains("install-icon"), "proper class is in image class");
|
||||
ok(img.classList.contains("camera-icon"), "proper class is in image class");
|
||||
|
||||
gIdentityHandler.setPermission("install", SitePermissions.getDefault("install"));
|
||||
SitePermissions.remove(gBrowser.currentURI, "camera");
|
||||
|
||||
gIdentityHandler._identityBox.click();
|
||||
ok(!is_hidden(emptyLabel), "List of permissions is empty");
|
||||
@ -62,25 +55,57 @@ add_task(function* testIdentityIcon() {
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
|
||||
|
||||
gIdentityHandler.setPermission("geo", SitePermissions.ALLOW);
|
||||
SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.ALLOW);
|
||||
|
||||
ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
|
||||
"identity-box signals granted permissions");
|
||||
|
||||
gIdentityHandler.setPermission("geo", SitePermissions.getDefault("geo"));
|
||||
SitePermissions.remove(gBrowser.currentURI, "geo");
|
||||
|
||||
ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
|
||||
"identity-box doesn't signal granted permissions");
|
||||
|
||||
gIdentityHandler.setPermission("camera", SitePermissions.BLOCK);
|
||||
SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK);
|
||||
|
||||
ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
|
||||
"identity-box doesn't signal granted permissions");
|
||||
|
||||
gIdentityHandler.setPermission("cookie", SitePermissions.SESSION);
|
||||
SitePermissions.set(gBrowser.currentURI, "cookie", SitePermissions.SESSION);
|
||||
|
||||
ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
|
||||
"identity-box signals granted permissions");
|
||||
|
||||
SitePermissions.remove(gBrowser.currentURI, "geo");
|
||||
SitePermissions.remove(gBrowser.currentURI, "camera");
|
||||
SitePermissions.remove(gBrowser.currentURI, "cookie");
|
||||
});
|
||||
|
||||
add_task(function* testCancelPermission() {
|
||||
let {gIdentityHandler} = gBrowser.ownerGlobal;
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
|
||||
|
||||
let permissionsList = document.getElementById("identity-popup-permission-list");
|
||||
let emptyLabel = permissionsList.nextSibling;
|
||||
|
||||
SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.ALLOW);
|
||||
SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.BLOCK);
|
||||
|
||||
gIdentityHandler._identityBox.click();
|
||||
|
||||
ok(is_hidden(emptyLabel), "List of permissions is not empty");
|
||||
|
||||
let cancelButtons = permissionsList
|
||||
.querySelectorAll(".identity-popup-permission-remove-button");
|
||||
|
||||
cancelButtons[0].click();
|
||||
let labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
|
||||
is(labels.length, 1, "One permission should be removed");
|
||||
cancelButtons[1].click();
|
||||
labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
|
||||
is(labels.length, 0, "One permission should be removed");
|
||||
|
||||
gIdentityHandler._identityPopup.hidden = true;
|
||||
});
|
||||
|
||||
add_task(function* testPermissionIcons() {
|
||||
@ -88,9 +113,9 @@ add_task(function* testPermissionIcons() {
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
|
||||
|
||||
gIdentityHandler.setPermission("camera", SitePermissions.ALLOW);
|
||||
gIdentityHandler.setPermission("geo", SitePermissions.BLOCK);
|
||||
gIdentityHandler.setPermission("microphone", SitePermissions.SESSION);
|
||||
SitePermissions.set(gBrowser.currentURI, "camera", SitePermissions.ALLOW);
|
||||
SitePermissions.set(gBrowser.currentURI, "geo", SitePermissions.BLOCK);
|
||||
SitePermissions.set(gBrowser.currentURI, "microphone", SitePermissions.SESSION);
|
||||
|
||||
let geoIcon = gIdentityHandler._identityBox.querySelector("[data-permission-id='geo']");
|
||||
ok(geoIcon.hasAttribute("showing"), "blocked permission icon is shown");
|
||||
@ -105,7 +130,7 @@ add_task(function* testPermissionIcons() {
|
||||
ok(!microphoneIcon.hasAttribute("showing"),
|
||||
"allowed permission icon is not shown");
|
||||
|
||||
gIdentityHandler.setPermission("geo", SitePermissions.getDefault("geo"));
|
||||
SitePermissions.remove(gBrowser.currentURI, "geo");
|
||||
|
||||
ok(!geoIcon.hasAttribute("showing"),
|
||||
"blocked permission icon is not shown after reset");
|
||||
|
@ -505,9 +505,7 @@ const CustomizableWidgets = [
|
||||
defaultArea: CustomizableUI.AREA_PANEL,
|
||||
onCommand: function(e) {
|
||||
let win = e.target.ownerGlobal;
|
||||
if (typeof win.OpenBrowserWindow == "function") {
|
||||
win.OpenBrowserWindow({private: true});
|
||||
}
|
||||
win.OpenBrowserWindow({private: true});
|
||||
}
|
||||
}, {
|
||||
id: "save-page-button",
|
||||
@ -516,9 +514,7 @@ const CustomizableWidgets = [
|
||||
defaultArea: CustomizableUI.AREA_PANEL,
|
||||
onCommand: function(aEvent) {
|
||||
let win = aEvent.target.ownerGlobal;
|
||||
if (typeof win.saveBrowser == "function") {
|
||||
win.saveBrowser(win.gBrowser.selectedBrowser);
|
||||
}
|
||||
win.saveBrowser(win.gBrowser.selectedBrowser);
|
||||
}
|
||||
}, {
|
||||
id: "find-button",
|
||||
@ -538,9 +534,7 @@ const CustomizableWidgets = [
|
||||
defaultArea: CustomizableUI.AREA_PANEL,
|
||||
onCommand: function(aEvent) {
|
||||
let win = aEvent.target.ownerGlobal;
|
||||
if (typeof win.BrowserOpenFileWindow == "function") {
|
||||
win.BrowserOpenFileWindow();
|
||||
}
|
||||
win.BrowserOpenFileWindow();
|
||||
}
|
||||
}, {
|
||||
id: "sidebar-button",
|
||||
@ -615,9 +609,7 @@ const CustomizableWidgets = [
|
||||
defaultArea: CustomizableUI.AREA_PANEL,
|
||||
onCommand: function(aEvent) {
|
||||
let win = aEvent.target.ownerGlobal;
|
||||
if (typeof win.BrowserOpenAddonsMgr == "function") {
|
||||
win.BrowserOpenAddonsMgr();
|
||||
}
|
||||
win.BrowserOpenAddonsMgr();
|
||||
}
|
||||
}, {
|
||||
id: "zoom-controls",
|
||||
@ -1186,9 +1178,7 @@ let preferencesButton = {
|
||||
defaultArea: CustomizableUI.AREA_PANEL,
|
||||
onCommand: function(aEvent) {
|
||||
let win = aEvent.target.ownerGlobal;
|
||||
if (typeof win.openPreferences == "function") {
|
||||
win.openPreferences();
|
||||
}
|
||||
win.openPreferences();
|
||||
}
|
||||
};
|
||||
if (AppConstants.platform == "win") {
|
||||
@ -1277,9 +1267,7 @@ if (AppConstants.E10S_TESTING_ONLY) {
|
||||
},
|
||||
onCommand: function(aEvent) {
|
||||
let win = aEvent.view;
|
||||
if (win && typeof win.OpenBrowserWindow == "function") {
|
||||
win.OpenBrowserWindow({remote: false});
|
||||
}
|
||||
win.OpenBrowserWindow({remote: false});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -394,6 +394,27 @@
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<!-- Call this when the height of one of your views (the main view or a
|
||||
subview) changes and you want the heights of the multiview and panel
|
||||
to be the same as the view's height. -->
|
||||
<method name="setHeightToFit">
|
||||
<body><![CDATA[
|
||||
// Set the max-height to zero, wait until the height is actually
|
||||
// updated, and then remove it. If it's not removed, weird things can
|
||||
// happen, like widgets in the panel won't respond to clicks even
|
||||
// though they're visible.
|
||||
let count = 5;
|
||||
let height = getComputedStyle(this).height;
|
||||
this.style.maxHeight = "0";
|
||||
let interval = setInterval(() => {
|
||||
if (height != getComputedStyle(this).height || --count == 0) {
|
||||
clearInterval(interval);
|
||||
this.style.removeProperty("max-height");
|
||||
}
|
||||
}, 0);
|
||||
]]></body>
|
||||
</method>
|
||||
|
||||
<method name="_heightOfSubview">
|
||||
<parameter name="aSubview"/>
|
||||
<parameter name="aContainerToCheck"/>
|
||||
|
@ -160,13 +160,6 @@ richlistitem.download button {
|
||||
|
||||
/*** Downloads panel multiview (main view and blocked-downloads subview) ***/
|
||||
|
||||
#downloadsPanel,
|
||||
#downloadsPanel .panel-viewstack[viewtype=main]:not([transitioning]) #downloadsPanel-mainView {
|
||||
/* Tiny hack to ensure the panel shrinks back to its original
|
||||
size after closing a subview that is bigger than the main view. */
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
/* Hide all the usual buttons. */
|
||||
#downloadsPanel-mainView .download-state[state="8"] .downloadCancel,
|
||||
#downloadsPanel-mainView .download-state[state="8"] .downloadConfirmBlock,
|
||||
|
@ -709,6 +709,8 @@ const DownloadsView = {
|
||||
DownloadsPanel.panel.removeAttribute("hasdownloads");
|
||||
}
|
||||
|
||||
DownloadsBlockedSubview.view.setHeightToFit();
|
||||
|
||||
// If we've got some hidden downloads, we should activate the
|
||||
// DownloadsSummary. The DownloadsSummary will determine whether or not
|
||||
// it's appropriate to actually display the summary.
|
||||
@ -1194,7 +1196,17 @@ const DownloadsViewController = {
|
||||
!(aCommand in DownloadsViewItem.prototype)) {
|
||||
return false;
|
||||
}
|
||||
// Secondly, determine if focus is on a control in the downloads list.
|
||||
// The currently supported commands depend on whether the blocked subview is
|
||||
// showing. If it is, then take the following path.
|
||||
if (DownloadsBlockedSubview.view.showingSubView) {
|
||||
let blockedSubviewCmds = [
|
||||
"downloadsCmd_chooseOpen",
|
||||
"cmd_delete",
|
||||
];
|
||||
return blockedSubviewCmds.indexOf(aCommand) >= 0;
|
||||
}
|
||||
// If the blocked subview is not showing, then determine if focus is on a
|
||||
// control in the downloads list.
|
||||
let element = document.commandDispatcher.focusedElement;
|
||||
while (element && element != DownloadsView.richListBox) {
|
||||
element = element.parentNode;
|
||||
@ -1604,6 +1616,9 @@ const DownloadsBlockedSubview = {
|
||||
*/
|
||||
hide() {
|
||||
this.view.showMainView();
|
||||
// The point of this is to focus the proper element in the panel now that
|
||||
// the main view is showing again. showPanel handles that.
|
||||
DownloadsPanel.showPanel();
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -39,7 +39,8 @@ add_task(function* mainTest() {
|
||||
// Click the Open button. The alert blocked-download dialog should be
|
||||
// shown.
|
||||
let dialogPromise = promiseAlertDialogOpen("cancel");
|
||||
DownloadsBlockedSubview.elements.openButton.click();
|
||||
EventUtils.synthesizeMouse(DownloadsBlockedSubview.elements.openButton,
|
||||
10, 10, {}, window);
|
||||
yield dialogPromise;
|
||||
|
||||
window.focus();
|
||||
@ -53,7 +54,8 @@ add_task(function* mainTest() {
|
||||
|
||||
// Click the Remove button. The panel should close and the item should be
|
||||
// removed from it.
|
||||
DownloadsBlockedSubview.elements.deleteButton.click();
|
||||
EventUtils.synthesizeMouse(DownloadsBlockedSubview.elements.deleteButton,
|
||||
10, 10, {}, window);
|
||||
yield promisePanelHidden();
|
||||
yield openPanel();
|
||||
|
||||
@ -150,16 +152,16 @@ function makeDownload(verdict) {
|
||||
}
|
||||
|
||||
function promiseSubviewShown(shown) {
|
||||
// More terribleness, but I'm tired of fighting intermittent timeouts on try.
|
||||
// Just poll for the subview and wait a second before resolving the promise.
|
||||
return new Promise(resolve => {
|
||||
if (shown == DownloadsBlockedSubview.view.showingSubView) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
let event = shown ? "ViewShowing" : "ViewHiding";
|
||||
let subview = DownloadsBlockedSubview.subview;
|
||||
subview.addEventListener(event, function showing() {
|
||||
subview.removeEventListener(event, showing);
|
||||
resolve();
|
||||
});
|
||||
let interval = setInterval(() => {
|
||||
if (shown == DownloadsBlockedSubview.view.showingSubView &&
|
||||
!DownloadsBlockedSubview.view._transitioning) {
|
||||
clearInterval(interval);
|
||||
setTimeout(resolve, 1000);
|
||||
return;
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
@ -75,6 +75,11 @@ class BasePopup {
|
||||
this.browserStyle = browserStyle;
|
||||
this.window = viewNode.ownerGlobal;
|
||||
|
||||
this.panel = this.viewNode;
|
||||
while (this.panel.localName != "panel") {
|
||||
this.panel = this.panel.parentNode;
|
||||
}
|
||||
|
||||
this.contentReady = new Promise(resolve => {
|
||||
this._resolveContentReady = resolve;
|
||||
});
|
||||
@ -93,6 +98,7 @@ class BasePopup {
|
||||
this.browser.removeEventListener("DOMWindowClose", this, true);
|
||||
this.browser.removeEventListener("MozScrolledAreaChanged", this, true);
|
||||
this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
|
||||
this.viewNode.style.maxHeight = "";
|
||||
this.browser.remove();
|
||||
|
||||
this.browser = null;
|
||||
@ -106,6 +112,10 @@ class BasePopup {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
get fixedWidth() {
|
||||
return false;
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
switch (event.type) {
|
||||
case this.DESTROY_EVENT:
|
||||
@ -166,15 +176,16 @@ class BasePopup {
|
||||
this.browser.setAttribute("disableglobalhistory", "true");
|
||||
this.browser.setAttribute("webextension-view-type", "popup");
|
||||
|
||||
// We only need flex sizing for the sake of the slide-in sub-views of the
|
||||
// main menu panel, so that the browser occupies the full width of the view,
|
||||
// and also takes up any extra height that's available to it.
|
||||
this.browser.setAttribute("flex", "1");
|
||||
|
||||
// Note: When using noautohide panels, the popup manager will add width and
|
||||
// height attributes to the panel, breaking our resize code, if the browser
|
||||
// starts out smaller than 30px by 10px. This isn't an issue now, but it
|
||||
// will be if and when we popup debugging.
|
||||
|
||||
// This overrides the content's preferred size when displayed in a
|
||||
// fixed-size, slide-in panel.
|
||||
this.browser.setAttribute("flex", "1");
|
||||
|
||||
viewNode.appendChild(this.browser);
|
||||
|
||||
return new Promise(resolve => {
|
||||
@ -217,28 +228,76 @@ class BasePopup {
|
||||
return;
|
||||
}
|
||||
|
||||
let width, height;
|
||||
try {
|
||||
let w = {}, h = {};
|
||||
this.browser.docShell.contentViewer.getContentSize(w, h);
|
||||
if (this.fixedWidth) {
|
||||
// If we're in a fixed-width area (namely a slide-in subview of the main
|
||||
// menu panel), we need to calculate the view height based on the
|
||||
// preferred height of the content document's root scrollable element at the
|
||||
// current width, rather than the complete preferred dimensions of the
|
||||
// content window.
|
||||
|
||||
width = w.value / this.window.devicePixelRatio;
|
||||
height = h.value / this.window.devicePixelRatio;
|
||||
let doc = this.browser.contentDocument;
|
||||
if (!doc || !doc.documentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The width calculation is imperfect, and is often a fraction of a pixel
|
||||
// too narrow, even after taking the ceiling, which causes lines of text
|
||||
// to wrap.
|
||||
width += 1;
|
||||
} catch (e) {
|
||||
// getContentSize can throw
|
||||
[width, height] = [400, 400];
|
||||
let root = doc.documentElement;
|
||||
let body = doc.body;
|
||||
if (!body || doc.compatMode == "BackCompat") {
|
||||
// In quirks mode, the root element is used as the scroll frame, and the
|
||||
// body lies about its scroll geometry, and returns the values for the
|
||||
// root instead.
|
||||
body = root;
|
||||
}
|
||||
|
||||
// Compensate for any offsets (margin, padding, ...) between the scroll
|
||||
// area of the body and the outer height of the document.
|
||||
let getHeight = elem => elem.getBoundingClientRect(elem).height;
|
||||
let bodyPadding = getHeight(root) - getHeight(body);
|
||||
|
||||
let height = Math.ceil(body.scrollHeight + bodyPadding);
|
||||
|
||||
// Figure out how much extra space we have on the side of the panel
|
||||
// opposite the arrow.
|
||||
let side = this.panel.getAttribute("side") == "top" ? "bottom" : "top";
|
||||
let maxHeight = this.viewHeight + this.extraHeight[side];
|
||||
|
||||
height = Math.min(height, maxHeight);
|
||||
this.browser.style.height = `${height}px`;
|
||||
|
||||
// Set a maximum height on the <panelview> element to our preferred
|
||||
// maximum height, so that the PanelUI resizing code can make an accurate
|
||||
// calculation. If we don't do this, the flex sizing logic will prevent us
|
||||
// from ever reporting a preferred size smaller than the height currently
|
||||
// available to us in the panel.
|
||||
height = Math.max(height, this.viewHeight);
|
||||
this.viewNode.style.maxHeight = `${height}px`;
|
||||
} else {
|
||||
let width, height;
|
||||
try {
|
||||
let w = {}, h = {};
|
||||
this.browser.docShell.contentViewer.getContentSize(w, h);
|
||||
|
||||
width = w.value / this.window.devicePixelRatio;
|
||||
height = h.value / this.window.devicePixelRatio;
|
||||
|
||||
// The width calculation is imperfect, and is often a fraction of a pixel
|
||||
// too narrow, even after taking the ceiling, which causes lines of text
|
||||
// to wrap.
|
||||
width += 1;
|
||||
} catch (e) {
|
||||
// getContentSize can throw
|
||||
[width, height] = [400, 400];
|
||||
}
|
||||
|
||||
width = Math.ceil(Math.min(width, 800));
|
||||
height = Math.ceil(Math.min(height, 600));
|
||||
|
||||
this.browser.style.width = `${width}px`;
|
||||
this.browser.style.height = `${height}px`;
|
||||
}
|
||||
|
||||
width = Math.ceil(Math.min(width, 800));
|
||||
height = Math.ceil(Math.min(height, 600));
|
||||
|
||||
this.browser.style.width = `${width}px`;
|
||||
this.browser.style.height = `${height}px`;
|
||||
let event = new this.window.CustomEvent("WebExtPopupResized");
|
||||
this.browser.dispatchEvent(event);
|
||||
|
||||
this._resolveContentReady();
|
||||
}
|
||||
@ -283,10 +342,36 @@ global.PanelPopup = class PanelPopup extends BasePopup {
|
||||
};
|
||||
|
||||
global.ViewPopup = class ViewPopup extends BasePopup {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
// Store the initial height of the view, so that we never resize menu panel
|
||||
// sub-views smaller than the initial height of the menu.
|
||||
this.viewHeight = this.viewNode.boxObject.height;
|
||||
|
||||
// Calculate the extra height available on the screen above and below the
|
||||
// menu panel. Use that to calculate the how much the sub-view may grow.
|
||||
let popupRect = this.panel.getBoundingClientRect();
|
||||
|
||||
let win = this.window;
|
||||
let popupBottom = win.mozInnerScreenY + popupRect.bottom;
|
||||
let popupTop = win.mozInnerScreenY + popupRect.top;
|
||||
|
||||
let screenBottom = win.screen.availTop + win.screen.availHeight;
|
||||
this.extraHeight = {
|
||||
bottom: Math.max(0, screenBottom - popupBottom),
|
||||
top: Math.max(0, popupTop - win.screen.availTop),
|
||||
};
|
||||
}
|
||||
|
||||
get DESTROY_EVENT() {
|
||||
return "ViewHiding";
|
||||
}
|
||||
|
||||
get fixedWidth() {
|
||||
return !this.viewNode.classList.contains("cui-widget-panelview");
|
||||
}
|
||||
|
||||
closePopup() {
|
||||
CustomizableUI.hidePanelForNode(this.viewNode);
|
||||
}
|
||||
@ -299,6 +384,7 @@ global.TabContext = function TabContext(getDefaults, extension) {
|
||||
this.getDefaults = getDefaults;
|
||||
|
||||
this.tabData = new WeakMap();
|
||||
this.lastLocation = new WeakMap();
|
||||
|
||||
AllWindowEvents.addListener("progress", this);
|
||||
AllWindowEvents.addListener("TabSelect", this);
|
||||
@ -327,12 +413,24 @@ TabContext.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
onStateChange(browser, webProgress, request, stateFlags, statusCode) {
|
||||
let flags = Ci.nsIWebProgressListener;
|
||||
|
||||
if (!(~stateFlags & (flags.STATE_IS_WINDOW | flags.STATE_START) ||
|
||||
this.lastLocation.has(browser))) {
|
||||
this.lastLocation.set(browser, request.URI);
|
||||
}
|
||||
},
|
||||
|
||||
onLocationChange(browser, webProgress, request, locationURI, flags) {
|
||||
let gBrowser = browser.ownerGlobal.gBrowser;
|
||||
if (browser === gBrowser.selectedBrowser) {
|
||||
let lastLocation = this.lastLocation.get(browser);
|
||||
if (browser === gBrowser.selectedBrowser &&
|
||||
!(lastLocation && lastLocation.equalsExceptRef(browser.currentURI))) {
|
||||
let tab = gBrowser.getTabForBrowser(browser);
|
||||
this.emit("location-change", tab, true);
|
||||
}
|
||||
this.lastLocation.set(browser, browser.currentURI);
|
||||
},
|
||||
|
||||
shutdown() {
|
||||
|
@ -2,7 +2,31 @@
|
||||
/* vim: set sts=2 sw=2 et tw=80: */
|
||||
"use strict";
|
||||
|
||||
add_task(function* testPageActionPopupResize() {
|
||||
function* openPanel(extension, win = window) {
|
||||
clickBrowserAction(extension, win);
|
||||
|
||||
let {target} = yield BrowserTestUtils.waitForEvent(win.document, "load", true, (event) => {
|
||||
return event.target.location && event.target.location.href.endsWith("popup.html");
|
||||
});
|
||||
|
||||
return target.defaultView
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler;
|
||||
}
|
||||
|
||||
function* awaitResize(browser) {
|
||||
// Debouncing code makes this a bit racy.
|
||||
// Try to skip the first, early resize, and catch the resize event we're
|
||||
// looking for, but don't wait longer than a few seconds.
|
||||
|
||||
return Promise.race([
|
||||
BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")
|
||||
.then(() => BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")),
|
||||
new Promise(resolve => setTimeout(resolve, 5000)),
|
||||
]);
|
||||
}
|
||||
|
||||
add_task(function* testBrowserActionPopupResize() {
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"browser_action": {
|
||||
@ -12,7 +36,7 @@ add_task(function* testPageActionPopupResize() {
|
||||
},
|
||||
|
||||
files: {
|
||||
"popup.html": "<html><head><meta charset=\"utf-8\"></head></html>",
|
||||
"popup.html": '<html><head><meta charset="utf-8"></head></html>',
|
||||
},
|
||||
});
|
||||
|
||||
@ -20,13 +44,9 @@ add_task(function* testPageActionPopupResize() {
|
||||
|
||||
clickBrowserAction(extension, window);
|
||||
|
||||
let {target: panelDocument} = yield BrowserTestUtils.waitForEvent(document, "load", true, (event) => {
|
||||
info(`Loaded ${event.target.location}`);
|
||||
return event.target.location && event.target.location.href.endsWith("popup.html");
|
||||
});
|
||||
|
||||
let panelWindow = panelDocument.defaultView;
|
||||
let panelBody = panelDocument.body;
|
||||
let browser = yield openPanel(extension);
|
||||
let panelWindow = browser.contentWindow;
|
||||
let panelBody = panelWindow.document.body;
|
||||
|
||||
function checkSize(expected) {
|
||||
is(panelWindow.innerHeight, expected, `Panel window should be ${expected}px tall`);
|
||||
@ -52,10 +72,240 @@ add_task(function* testPageActionPopupResize() {
|
||||
|
||||
for (let size of sizes) {
|
||||
setSize(size);
|
||||
yield BrowserTestUtils.waitForEvent(panelWindow, "resize");
|
||||
yield awaitResize(browser);
|
||||
checkSize(size);
|
||||
}
|
||||
|
||||
yield closeBrowserAction(extension);
|
||||
yield extension.unload();
|
||||
});
|
||||
|
||||
function* testPopupSize(standardsMode, browserWin = window, arrowSide = "top") {
|
||||
let docType = standardsMode ? "<!DOCTYPE html>" : "";
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
manifest: {
|
||||
"browser_action": {
|
||||
"default_popup": "popup.html",
|
||||
"browser_style": false,
|
||||
},
|
||||
},
|
||||
|
||||
files: {
|
||||
"popup.html": `${docType}
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style type="text/css">
|
||||
body > span {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 150px;
|
||||
border: 2px solid black;
|
||||
}
|
||||
.big > span {
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
}
|
||||
.bigger > span {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.huge > span {
|
||||
height: ${2 * screen.height}px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
<span></span>
|
||||
</body>
|
||||
</html>`,
|
||||
},
|
||||
});
|
||||
|
||||
yield extension.startup();
|
||||
|
||||
if (arrowSide == "top") {
|
||||
// Test the standalone panel for a toolbar button.
|
||||
let browser = yield openPanel(extension, browserWin);
|
||||
let win = browser.contentWindow;
|
||||
let body = win.document.body;
|
||||
|
||||
let isStandards = win.document.compatMode != "BackCompat";
|
||||
is(isStandards, standardsMode, "Document has the expected compat mode");
|
||||
|
||||
let {innerWidth, innerHeight} = win;
|
||||
|
||||
body.classList.add("bigger");
|
||||
yield awaitResize(browser);
|
||||
|
||||
is(win.innerHeight, innerHeight, "Window height should not change");
|
||||
ok(win.innerWidth > innerWidth, `Window width should increase (${win.innerWidth} > ${innerWidth})`);
|
||||
|
||||
|
||||
body.classList.remove("bigger");
|
||||
yield awaitResize(browser);
|
||||
|
||||
is(win.innerHeight, innerHeight, "Window height should not change");
|
||||
|
||||
// The getContentSize calculation is not always reliable to single-pixel
|
||||
// precision.
|
||||
ok(Math.abs(win.innerWidth - innerWidth) <= 1,
|
||||
`Window width should return to approximately its original value (${win.innerWidth} ~= ${innerWidth})`);
|
||||
|
||||
yield closeBrowserAction(extension, browserWin);
|
||||
}
|
||||
|
||||
|
||||
// Test the PanelUI panel for a menu panel button.
|
||||
let widget = getBrowserActionWidget(extension);
|
||||
CustomizableUI.addWidgetToArea(widget.id, CustomizableUI.AREA_PANEL);
|
||||
|
||||
let browser = yield openPanel(extension, browserWin);
|
||||
let win = browser.contentWindow;
|
||||
let body = win.document.body;
|
||||
|
||||
let {panel} = browserWin.PanelUI;
|
||||
let origPanelRect = panel.getBoundingClientRect();
|
||||
|
||||
// Check that the panel is still positioned as expected.
|
||||
let checkPanelPosition = () => {
|
||||
is(panel.getAttribute("side"), arrowSide, "Panel arrow is positioned as expected");
|
||||
|
||||
let panelRect = panel.getBoundingClientRect();
|
||||
if (arrowSide == "top") {
|
||||
ok(panelRect.top, origPanelRect.top, "Panel has not moved downwards");
|
||||
ok(panelRect.bottom >= origPanelRect.bottom, `Panel has not shrunk from original size (${panelRect.bottom} >= ${origPanelRect.bottom})`);
|
||||
|
||||
let screenBottom = browserWin.screen.availTop + win.screen.availHeight;
|
||||
let panelBottom = browserWin.mozInnerScreenY + panelRect.bottom;
|
||||
ok(panelBottom <= screenBottom, `Bottom of popup should be on-screen. (${panelBottom} <= ${screenBottom})`);
|
||||
} else {
|
||||
ok(panelRect.bottom, origPanelRect.bottom, "Panel has not moved upwards");
|
||||
ok(panelRect.top <= origPanelRect.top, `Panel has not shrunk from original size (${panelRect.top} <= ${origPanelRect.top})`);
|
||||
|
||||
let panelTop = browserWin.mozInnerScreenY + panelRect.top;
|
||||
ok(panelTop >= browserWin.screen.availTop, `Top of popup should be on-screen. (${panelTop} >= ${browserWin.screen.availTop})`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let isStandards = win.document.compatMode != "BackCompat";
|
||||
is(isStandards, standardsMode, "Document has the expected compat mode");
|
||||
|
||||
// Wait long enough to make sure the initial resize debouncing timer has
|
||||
// expired.
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// If the browser's preferred height is smaller than the initial height of the
|
||||
// panel, then it will still take up the full available vertical space. Even
|
||||
// so, we need to check that we've gotten the preferred height calculation
|
||||
// correct, so check that explicitly.
|
||||
let getHeight = () => parseFloat(browser.style.height);
|
||||
|
||||
let {innerWidth, innerHeight} = win;
|
||||
let height = getHeight();
|
||||
|
||||
|
||||
info("Increase body children's width. " +
|
||||
"Expect them to wrap, and the frame to grow vertically rather than widen.");
|
||||
body.className = "big";
|
||||
yield awaitResize(browser);
|
||||
|
||||
ok(getHeight() > height, `Browser height should increase (${getHeight()} > ${height})`);
|
||||
|
||||
is(win.innerWidth, innerWidth, "Window width should not change");
|
||||
ok(win.innerHeight >= innerHeight, `Window height should increase (${win.innerHeight} >= ${innerHeight})`);
|
||||
is(win.scrollMaxY, 0, "Document should not be vertically scrollable");
|
||||
|
||||
checkPanelPosition();
|
||||
|
||||
|
||||
info("Increase body children's width and height. " +
|
||||
"Expect them to wrap, and the frame to grow vertically rather than widen.");
|
||||
body.className = "bigger";
|
||||
yield awaitResize(browser);
|
||||
|
||||
ok(getHeight() > height, `Browser height should increase (${getHeight()} > ${height})`);
|
||||
|
||||
is(win.innerWidth, innerWidth, "Window width should not change");
|
||||
ok(win.innerHeight >= innerHeight, `Window height should increase (${win.innerHeight} >= ${innerHeight})`);
|
||||
is(win.scrollMaxY, 0, "Document should not be vertically scrollable");
|
||||
|
||||
checkPanelPosition();
|
||||
|
||||
|
||||
info("Increase body height beyond the height of the screen. " +
|
||||
"Expect the panel to grow to accommodate, but not larger than the height of the screen.");
|
||||
body.className = "huge";
|
||||
yield awaitResize(browser);
|
||||
|
||||
ok(getHeight() > height, `Browser height should increase (${getHeight()} > ${height})`);
|
||||
|
||||
is(win.innerWidth, innerWidth, "Window width should not change");
|
||||
ok(win.innerHeight > innerHeight, `Window height should increase (${win.innerHeight} > ${innerHeight})`);
|
||||
ok(win.innerHeight < screen.height, `Window height be less than the screen height (${win.innerHeight} < ${screen.height})`);
|
||||
ok(win.scrollMaxY > 0, `Document should be vertically scrollable (${win.scrollMaxY} > 0)`);
|
||||
|
||||
checkPanelPosition();
|
||||
|
||||
|
||||
info("Restore original styling. Expect original dimensions.");
|
||||
body.className = "";
|
||||
yield awaitResize(browser);
|
||||
|
||||
is(getHeight(), height, "Browser height should return to its original value");
|
||||
|
||||
is(win.innerWidth, innerWidth, "Window width should not change");
|
||||
is(win.innerHeight, innerHeight, "Window height should return to its original value");
|
||||
is(win.scrollMaxY, 0, "Document should not be vertically scrollable");
|
||||
|
||||
checkPanelPosition();
|
||||
|
||||
yield closeBrowserAction(extension, browserWin);
|
||||
|
||||
yield extension.unload();
|
||||
}
|
||||
|
||||
add_task(function* testBrowserActionMenuResizeStandards() {
|
||||
yield testPopupSize(true);
|
||||
});
|
||||
|
||||
add_task(function* testBrowserActionMenuResizeQuirks() {
|
||||
yield testPopupSize(false);
|
||||
});
|
||||
|
||||
// Test that we still make reasonable maximum size calculations when the window
|
||||
// is close enough to the bottom of the screen that the menu panel opens above,
|
||||
// rather than below, its button.
|
||||
add_task(function* testBrowserActionMenuResizeBottomArrow() {
|
||||
const WIDTH = 800;
|
||||
const HEIGHT = 300;
|
||||
|
||||
let left = screen.availLeft + screen.availWidth - WIDTH;
|
||||
let top = screen.availTop + screen.availHeight - HEIGHT;
|
||||
|
||||
let win = yield BrowserTestUtils.openNewBrowserWindow();
|
||||
|
||||
win.resizeTo(WIDTH, HEIGHT);
|
||||
|
||||
// Sometimes we run into problems on Linux with resizing being asynchronous
|
||||
// and window managers not allowing us to move the window so that any part of
|
||||
// it is off-screen, so we need to try more than once.
|
||||
for (let i = 0; i < 20; i++) {
|
||||
win.moveTo(left, top);
|
||||
|
||||
if (win.screenX == left && win.screenY == top) {
|
||||
break;
|
||||
}
|
||||
|
||||
yield new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
yield testPopupSize(true, win, "bottom");
|
||||
|
||||
yield BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
@ -247,6 +247,15 @@ add_task(function* testTabSwitchContext() {
|
||||
expect(details[2]);
|
||||
});
|
||||
},
|
||||
expect => {
|
||||
browser.test.log("Change the hash. Expect same properties.");
|
||||
|
||||
promiseTabLoad({id: tabs[1], url: "about:blank?0#ref"}).then(() => {
|
||||
expect(details[2]);
|
||||
});
|
||||
|
||||
browser.tabs.update(tabs[1], {url: "about:blank?0#ref"});
|
||||
},
|
||||
expect => {
|
||||
browser.test.log("Clear the title. Expect default title.");
|
||||
browser.pageAction.setTitle({tabId: tabs[1], title: ""});
|
||||
|
@ -85,6 +85,36 @@ add_task(function* testExecuteScript() {
|
||||
error.message, "Got expected error");
|
||||
}),
|
||||
|
||||
browser.tabs.executeScript({
|
||||
frameId: Number.MAX_SAFE_INTEGER,
|
||||
code: "42",
|
||||
}).then(result => {
|
||||
browser.test.fail("Expected error when specifying invalid frame ID");
|
||||
}, error => {
|
||||
let details = {
|
||||
frame_id: Number.MAX_SAFE_INTEGER,
|
||||
matchesHost: ["http://mochi.test/", "http://example.com/"],
|
||||
};
|
||||
browser.test.assertEq(`No window matching ${JSON.stringify(details)}`,
|
||||
error.message, "Got expected error");
|
||||
}),
|
||||
|
||||
browser.tabs.create({url: "http://example.net/", active: false}).then(tab => {
|
||||
return browser.tabs.executeScript(tab.id, {
|
||||
code: "42",
|
||||
}).then(result => {
|
||||
browser.test.fail("Expected error when trying to execute on invalid domain");
|
||||
}, error => {
|
||||
let details = {
|
||||
matchesHost: ["http://mochi.test/", "http://example.com/"],
|
||||
};
|
||||
browser.test.assertEq(`No window matching ${JSON.stringify(details)}`,
|
||||
error.message, "Got expected error");
|
||||
}).then(() => {
|
||||
return browser.tabs.remove(tab.id);
|
||||
});
|
||||
}),
|
||||
|
||||
browser.tabs.executeScript({
|
||||
code: "Promise.resolve(42)",
|
||||
}).then(result => {
|
||||
|
@ -105,6 +105,9 @@
|
||||
name="browser.taskbar.previews.enable"
|
||||
type="bool"/>
|
||||
#endif
|
||||
<preference id="browser.ctrlTab.previews"
|
||||
name="browser.ctrlTab.previews"
|
||||
type="bool"/>
|
||||
</preferences>
|
||||
|
||||
<hbox id="header-general"
|
||||
@ -268,6 +271,11 @@
|
||||
<groupbox data-category="paneGeneral"
|
||||
hidden="true" align="start">
|
||||
<caption><label>&tabsGroup.label;</label></caption>
|
||||
|
||||
<checkbox id="ctrlTabRecentlyUsedOrder" label="&ctrlTabRecentlyUsedOrder.label;"
|
||||
accesskey="&ctrlTabRecentlyUsedOrder.accesskey;"
|
||||
preference="browser.ctrlTab.previews"/>
|
||||
|
||||
<checkbox id="linkTargeting" label="&newWindowsAsTabs.label;"
|
||||
accesskey="&newWindowsAsTabs.accesskey;"
|
||||
preference="browser.link.open_newwindow"
|
||||
|
@ -106,6 +106,18 @@ var gSearchPane = {
|
||||
handleEvent: function(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "click":
|
||||
if (aEvent.target.id != "engineChildren" && aEvent.target.id != "removeEngineButton") {
|
||||
let engineList = document.getElementById("engineList");
|
||||
// We don't want to toggle off selection while editing keyword
|
||||
// so proceed only when the input field is hidden
|
||||
if (engineList.inputField.hidden) {
|
||||
let selection = engineList.view.selection;
|
||||
if (selection.count > 0) {
|
||||
selection.toggleSelect(selection.currentIndex);
|
||||
}
|
||||
engineList.blur();
|
||||
}
|
||||
}
|
||||
if (aEvent.target.id == "addEngines" && aEvent.button == 0) {
|
||||
Services.wm.getMostRecentWindow('navigator:browser')
|
||||
.BrowserSearch.loadAddEngines();
|
||||
|
@ -1,3 +1,3 @@
|
||||
This is the pdf.js project output, https://github.com/mozilla/pdf.js
|
||||
|
||||
Current extension version is: 1.5.345
|
||||
Current extension version is: 1.5.365
|
||||
|
@ -28,8 +28,8 @@ factory((root.pdfjsDistBuildPdf = {}));
|
||||
// Use strict in our context only - users might not want it
|
||||
'use strict';
|
||||
|
||||
var pdfjsVersion = '1.5.345';
|
||||
var pdfjsBuild = '10f9f11';
|
||||
var pdfjsVersion = '1.5.365';
|
||||
var pdfjsBuild = '19105f0';
|
||||
|
||||
var pdfjsFilePath =
|
||||
typeof document !== 'undefined' && document.currentScript ?
|
||||
@ -6771,6 +6771,8 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
|
||||
* @typedef {Object} getTextContentParameters
|
||||
* @param {boolean} normalizeWhitespace - replaces all occurrences of
|
||||
* whitespace with standard spaces (0x20). The default value is `false`.
|
||||
* @param {boolean} disableCombineTextItems - do not attempt to combine
|
||||
* same line {@link TextItem}'s. The default value is `false`.
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -7062,11 +7064,12 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
|
||||
* object that represent the page text content.
|
||||
*/
|
||||
getTextContent: function PDFPageProxy_getTextContent(params) {
|
||||
var normalizeWhitespace = (params && params.normalizeWhitespace) || false;
|
||||
|
||||
return this.transport.messageHandler.sendWithPromise('GetTextContent', {
|
||||
pageIndex: this.pageNumber - 1,
|
||||
normalizeWhitespace: normalizeWhitespace,
|
||||
normalizeWhitespace: (params && params.normalizeWhitespace === true ?
|
||||
true : /* Default */ false),
|
||||
combineTextItems: (params && params.disableCombineTextItems === true ?
|
||||
false : /* Default */ true),
|
||||
});
|
||||
},
|
||||
|
||||
|
113
browser/extensions/pdfjs/content/build/pdf.worker.js
vendored
113
browser/extensions/pdfjs/content/build/pdf.worker.js
vendored
@ -28,8 +28,8 @@ factory((root.pdfjsDistBuildPdfWorker = {}));
|
||||
// Use strict in our context only - users might not want it
|
||||
'use strict';
|
||||
|
||||
var pdfjsVersion = '1.5.345';
|
||||
var pdfjsBuild = '10f9f11';
|
||||
var pdfjsVersion = '1.5.365';
|
||||
var pdfjsBuild = '19105f0';
|
||||
|
||||
var pdfjsFilePath =
|
||||
typeof document !== 'undefined' && document.currentScript ?
|
||||
@ -36506,8 +36506,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
return errorFont();
|
||||
}
|
||||
|
||||
// We are holding font.translated references just for fontRef that are not
|
||||
// dictionaries (Dict). See explanation below.
|
||||
// We are holding `font.translated` references just for `fontRef`s that
|
||||
// are not actually `Ref`s, but rather `Dict`s. See explanation below.
|
||||
if (font.translated) {
|
||||
return font.translated;
|
||||
}
|
||||
@ -36516,7 +36516,12 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
|
||||
var preEvaluatedFont = this.preEvaluateFont(font, xref);
|
||||
var descriptor = preEvaluatedFont.descriptor;
|
||||
var fontID = fontRef.num + '_' + fontRef.gen;
|
||||
|
||||
var fontRefIsRef = isRef(fontRef), fontID;
|
||||
if (fontRefIsRef) {
|
||||
fontID = fontRef.toString();
|
||||
}
|
||||
|
||||
if (isDict(descriptor)) {
|
||||
if (!descriptor.fontAliases) {
|
||||
descriptor.fontAliases = Object.create(null);
|
||||
@ -36526,36 +36531,53 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
var hash = preEvaluatedFont.hash;
|
||||
if (fontAliases[hash]) {
|
||||
var aliasFontRef = fontAliases[hash].aliasRef;
|
||||
if (aliasFontRef && this.fontCache.has(aliasFontRef)) {
|
||||
if (fontRefIsRef && aliasFontRef &&
|
||||
this.fontCache.has(aliasFontRef)) {
|
||||
this.fontCache.putAlias(fontRef, aliasFontRef);
|
||||
return this.fontCache.get(fontRef);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fontAliases[hash]) {
|
||||
} else {
|
||||
fontAliases[hash] = {
|
||||
fontID: Font.getFontID()
|
||||
};
|
||||
}
|
||||
|
||||
fontAliases[hash].aliasRef = fontRef;
|
||||
if (fontRefIsRef) {
|
||||
fontAliases[hash].aliasRef = fontRef;
|
||||
}
|
||||
fontID = fontAliases[hash].fontID;
|
||||
}
|
||||
|
||||
// Workaround for bad PDF generators that don't reference fonts
|
||||
// properly, i.e. by not using an object identifier.
|
||||
// Check if the fontRef is a Dict (as opposed to a standard object),
|
||||
// in which case we don't cache the font and instead reference it by
|
||||
// fontName in font.loadedName below.
|
||||
var fontRefIsDict = isDict(fontRef);
|
||||
if (!fontRefIsDict) {
|
||||
// Workaround for bad PDF generators that reference fonts incorrectly,
|
||||
// where `fontRef` is a `Dict` rather than a `Ref` (fixes bug946506.pdf).
|
||||
// In this case we should not put the font into `this.fontCache` (which is
|
||||
// a `RefSetCache`), since it's not meaningful to use a `Dict` as a key.
|
||||
//
|
||||
// However, if we don't cache the font it's not possible to remove it
|
||||
// when `cleanup` is triggered from the API, which causes issues on
|
||||
// subsequent rendering operations (see issue7403.pdf).
|
||||
// A simple workaround would be to just not hold `font.translated`
|
||||
// references in this case, but this would force us to unnecessarily load
|
||||
// the same fonts over and over.
|
||||
//
|
||||
// Instead, we cheat a bit by attempting to use a modified `fontID` as a
|
||||
// key in `this.fontCache`, to allow the font to be cached.
|
||||
// NOTE: This works because `RefSetCache` calls `toString()` on provided
|
||||
// keys. Also, since `fontRef` is used when getting cached fonts,
|
||||
// we'll not accidentally match fonts cached with the `fontID`.
|
||||
if (fontRefIsRef) {
|
||||
this.fontCache.put(fontRef, fontCapability.promise);
|
||||
} else {
|
||||
if (!fontID) {
|
||||
fontID = (this.uniquePrefix || 'F_') + (++this.idCounters.obj);
|
||||
}
|
||||
this.fontCache.put('id_' + fontID, fontCapability.promise);
|
||||
}
|
||||
assert(fontID, 'The "fontID" must be defined.');
|
||||
|
||||
// Keep track of each font we translated so the caller can
|
||||
// load them asynchronously before calling display on a page.
|
||||
font.loadedName = 'g_' + this.pdfManager.docId + '_f' + (fontRefIsDict ?
|
||||
fontName.replace(/\W/g, '') : fontID);
|
||||
font.loadedName = 'g_' + this.pdfManager.docId + '_f' + fontID;
|
||||
|
||||
font.translated = fontCapability.promise;
|
||||
|
||||
@ -36954,7 +36976,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
getTextContent:
|
||||
function PartialEvaluator_getTextContent(stream, task, resources,
|
||||
stateManager,
|
||||
normalizeWhitespace) {
|
||||
normalizeWhitespace,
|
||||
combineTextItems) {
|
||||
|
||||
stateManager = (stateManager || new StateManager(new TextState()));
|
||||
|
||||
@ -37265,7 +37288,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
var isSameTextLine = !textState.font ? false :
|
||||
((textState.font.vertical ? args[0] : args[1]) === 0);
|
||||
advance = args[0] - args[1];
|
||||
if (isSameTextLine && textContentItem.initialized &&
|
||||
if (combineTextItems &&
|
||||
isSameTextLine && textContentItem.initialized &&
|
||||
advance > 0 &&
|
||||
advance <= textContentItem.fakeMultiSpaceMax) {
|
||||
textState.translateTextLineMatrix(args[0], args[1]);
|
||||
@ -37297,7 +37321,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
// Optimization to treat same line movement as advance.
|
||||
advance = textState.calcTextLineMatrixAdvance(
|
||||
args[0], args[1], args[2], args[3], args[4], args[5]);
|
||||
if (advance !== null && textContentItem.initialized &&
|
||||
if (combineTextItems &&
|
||||
advance !== null && textContentItem.initialized &&
|
||||
advance.value > 0 &&
|
||||
advance.value <= textContentItem.fakeMultiSpaceMax) {
|
||||
textState.translateTextLineMatrix(advance.width,
|
||||
@ -37438,7 +37463,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
|
||||
next(self.getTextContent(xobj, task,
|
||||
xobj.dict.get('Resources') || resources, stateManager,
|
||||
normalizeWhitespace).then(function (formTextContent) {
|
||||
normalizeWhitespace, combineTextItems).then(
|
||||
function (formTextContent) {
|
||||
Util.appendToArray(textContent.items, formTextContent.items);
|
||||
Util.extendObj(textContent.styles, formTextContent.styles);
|
||||
stateManager.restore();
|
||||
@ -37594,7 +37620,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
// the differences array only contains adobe standard or symbol set names,
|
||||
// in pratice it seems better to always try to create a toUnicode
|
||||
// map based of the default encoding.
|
||||
var toUnicode, charcode;
|
||||
var toUnicode, charcode, glyphName;
|
||||
if (!properties.composite /* is simple font */) {
|
||||
toUnicode = [];
|
||||
var encoding = properties.defaultEncoding.slice();
|
||||
@ -37602,12 +37628,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
// Merge in the differences array.
|
||||
var differences = properties.differences;
|
||||
for (charcode in differences) {
|
||||
encoding[charcode] = differences[charcode];
|
||||
glyphName = differences[charcode];
|
||||
if (glyphName === '.notdef') {
|
||||
// Skip .notdef to prevent rendering errors, e.g. boxes appearing
|
||||
// where there should be spaces (fixes issue5256.pdf).
|
||||
continue;
|
||||
}
|
||||
encoding[charcode] = glyphName;
|
||||
}
|
||||
var glyphsUnicodeMap = getGlyphsUnicode();
|
||||
for (charcode in encoding) {
|
||||
// a) Map the character code to a character name.
|
||||
var glyphName = encoding[charcode];
|
||||
glyphName = encoding[charcode];
|
||||
// b) Look up the character name in the Adobe Glyph List (see the
|
||||
// Bibliography) to obtain the corresponding Unicode value.
|
||||
if (glyphName === '') {
|
||||
@ -37959,7 +37991,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
if (isName(encoding)) {
|
||||
hash.update(encoding.name);
|
||||
} else if (isRef(encoding)) {
|
||||
hash.update(encoding.num + '_' + encoding.gen);
|
||||
hash.update(encoding.toString());
|
||||
} else if (isDict(encoding)) {
|
||||
var keys = encoding.getKeys();
|
||||
for (var i = 0, ii = keys.length; i < ii; i++) {
|
||||
@ -37967,7 +37999,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
|
||||
if (isName(entry)) {
|
||||
hash.update(entry.name);
|
||||
} else if (isRef(entry)) {
|
||||
hash.update(entry.num + '_' + entry.gen);
|
||||
hash.update(entry.toString());
|
||||
} else if (isArray(entry)) { // 'Differences' entry.
|
||||
// Ideally we should check the contents of the array, but to avoid
|
||||
// parsing it here and then again in |extractDataStructures|,
|
||||
@ -39165,13 +39197,14 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
|
||||
|
||||
// Determine the annotation's subtype.
|
||||
var subtype = dict.get('Subtype');
|
||||
subtype = isName(subtype) ? subtype.name : '';
|
||||
subtype = isName(subtype) ? subtype.name : null;
|
||||
|
||||
// Return the right annotation object based on the subtype and field type.
|
||||
var parameters = {
|
||||
xref: xref,
|
||||
dict: dict,
|
||||
ref: ref
|
||||
ref: ref,
|
||||
subtype: subtype,
|
||||
};
|
||||
|
||||
switch (subtype) {
|
||||
@ -39207,8 +39240,12 @@ AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
|
||||
return new FileAttachmentAnnotation(parameters);
|
||||
|
||||
default:
|
||||
warn('Unimplemented annotation type "' + subtype + '", ' +
|
||||
'falling back to base annotation');
|
||||
if (!subtype) {
|
||||
warn('Annotation is missing the required /Subtype.');
|
||||
} else {
|
||||
warn('Unimplemented annotation type "' + subtype + '", ' +
|
||||
'falling back to base annotation.');
|
||||
}
|
||||
return new Annotation(parameters);
|
||||
}
|
||||
}
|
||||
@ -39272,7 +39309,7 @@ var Annotation = (function AnnotationClosure() {
|
||||
// Expose public properties using a data object.
|
||||
this.data = {};
|
||||
this.data.id = params.ref.toString();
|
||||
this.data.subtype = dict.get('Subtype').name;
|
||||
this.data.subtype = params.subtype;
|
||||
this.data.annotationFlags = this.flags;
|
||||
this.data.rect = this.rectangle;
|
||||
this.data.color = this.color;
|
||||
@ -40280,7 +40317,8 @@ var Page = (function PageClosure() {
|
||||
},
|
||||
|
||||
extractTextContent: function Page_extractTextContent(task,
|
||||
normalizeWhitespace) {
|
||||
normalizeWhitespace,
|
||||
combineTextItems) {
|
||||
var handler = {
|
||||
on: function nullHandlerOn() {},
|
||||
send: function nullHandlerSend() {}
|
||||
@ -40313,7 +40351,8 @@ var Page = (function PageClosure() {
|
||||
task,
|
||||
self.resources,
|
||||
/* stateManager = */ null,
|
||||
normalizeWhitespace);
|
||||
normalizeWhitespace,
|
||||
combineTextItems);
|
||||
});
|
||||
},
|
||||
|
||||
@ -41577,12 +41616,14 @@ var WorkerMessageHandler = {
|
||||
handler.on('GetTextContent', function wphExtractText(data) {
|
||||
var pageIndex = data.pageIndex;
|
||||
var normalizeWhitespace = data.normalizeWhitespace;
|
||||
var combineTextItems = data.combineTextItems;
|
||||
return pdfManager.getPage(pageIndex).then(function(page) {
|
||||
var task = new WorkerTask('GetTextContent: page ' + pageIndex);
|
||||
startWorkerTask(task);
|
||||
var pageNum = pageIndex + 1;
|
||||
var start = Date.now();
|
||||
return page.extractTextContent(task, normalizeWhitespace).then(
|
||||
return page.extractTextContent(task, normalizeWhitespace,
|
||||
combineTextItems).then(
|
||||
function(textContent) {
|
||||
finishWorkerTask(task);
|
||||
info('text indexing: page=' + pageNum + ' - time=' +
|
||||
|
@ -2526,7 +2526,6 @@ exports.binarySearchFirstItem = binarySearchFirstItem;
|
||||
var event = document.createEvent('UIEvents');
|
||||
event.initUIEvent('pagechange', true, true, window, 0);
|
||||
event.pageNumber = e.pageNumber;
|
||||
event.previousPageNumber = e.previousPageNumber;
|
||||
e.source.container.dispatchEvent(event);
|
||||
});
|
||||
eventBus.on('pagesinit', function (e) {
|
||||
@ -5474,12 +5473,12 @@ var PDFPageView = (function PDFPageViewClosure() {
|
||||
function pdfPageRenderCallback() {
|
||||
pageViewDrawCallback(null);
|
||||
if (textLayer) {
|
||||
self.pdfPage.getTextContent({ normalizeWhitespace: true }).then(
|
||||
function textContentResolved(textContent) {
|
||||
textLayer.setTextContent(textContent);
|
||||
textLayer.render(TEXT_LAYER_RENDER_DELAY);
|
||||
}
|
||||
);
|
||||
self.pdfPage.getTextContent({
|
||||
normalizeWhitespace: true,
|
||||
}).then(function textContentResolved(textContent) {
|
||||
textLayer.setTextContent(textContent);
|
||||
textLayer.render(TEXT_LAYER_RENDER_DELAY);
|
||||
});
|
||||
}
|
||||
},
|
||||
function pdfPageRenderError(error) {
|
||||
@ -6357,6 +6356,9 @@ var PDFViewer = (function pdfViewer() {
|
||||
* @param {number} val - The page number.
|
||||
*/
|
||||
set currentPageNumber(val) {
|
||||
if ((val | 0) !== val) { // Ensure that `val` is an integer.
|
||||
throw new Error('Invalid page number.');
|
||||
}
|
||||
if (!this.pdfDocument) {
|
||||
this._currentPageNumber = val;
|
||||
return;
|
||||
@ -6376,22 +6378,14 @@ var PDFViewer = (function pdfViewer() {
|
||||
}
|
||||
return;
|
||||
}
|
||||
var arg;
|
||||
|
||||
if (!(0 < val && val <= this.pagesCount)) {
|
||||
arg = {
|
||||
source: this,
|
||||
pageNumber: this._currentPageNumber,
|
||||
previousPageNumber: val
|
||||
};
|
||||
this.eventBus.dispatch('pagechanging', arg);
|
||||
this.eventBus.dispatch('pagechange', arg);
|
||||
return;
|
||||
}
|
||||
|
||||
arg = {
|
||||
var arg = {
|
||||
source: this,
|
||||
pageNumber: val,
|
||||
previousPageNumber: this._currentPageNumber
|
||||
};
|
||||
this._currentPageNumber = val;
|
||||
this.eventBus.dispatch('pagechanging', arg);
|
||||
@ -6414,7 +6408,7 @@ var PDFViewer = (function pdfViewer() {
|
||||
* @param {number} val - Scale of the pages in percents.
|
||||
*/
|
||||
set currentScale(val) {
|
||||
if (isNaN(val)) {
|
||||
if (isNaN(val)) {
|
||||
throw new Error('Invalid numeric scale');
|
||||
}
|
||||
if (!this.pdfDocument) {
|
||||
@ -6975,7 +6969,9 @@ var PDFViewer = (function pdfViewer() {
|
||||
|
||||
getPageTextContent: function (pageIndex) {
|
||||
return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
|
||||
return page.getTextContent({ normalizeWhitespace: true });
|
||||
return page.getTextContent({
|
||||
normalizeWhitespace: true,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@ -8333,11 +8329,12 @@ function webViewerInitialized() {
|
||||
});
|
||||
|
||||
appConfig.toolbar.pageNumber.addEventListener('change', function() {
|
||||
// Handle the user inputting a floating point number.
|
||||
PDFViewerApplication.page = (this.value | 0);
|
||||
|
||||
if (this.value !== (this.value | 0).toString()) {
|
||||
this.value = PDFViewerApplication.page;
|
||||
// Ensure that the page number input displays the correct value, even if the
|
||||
// value entered by the user was invalid (e.g. a floating point number).
|
||||
if (this.value !== PDFViewerApplication.page.toString()) {
|
||||
PDFViewerApplication._updateUIToolbar({});
|
||||
}
|
||||
});
|
||||
|
||||
@ -8693,8 +8690,8 @@ function webViewerPageChanging(e) {
|
||||
PDFViewerApplication._updateUIToolbar({
|
||||
pageNumber: page,
|
||||
});
|
||||
if (e.previousPageNumber !== page &&
|
||||
PDFViewerApplication.pdfSidebar.isThumbnailViewVisible) {
|
||||
|
||||
if (PDFViewerApplication.pdfSidebar.isThumbnailViewVisible) {
|
||||
PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(page);
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@
|
||||
<!ENTITY alwaysAsk.accesskey "A">
|
||||
|
||||
<!ENTITY alwaysCheckDefault2.label "Always check if &brandShortName; is your default browser">
|
||||
<!ENTITY alwaysCheckDefault2.accesskey "w">
|
||||
<!ENTITY alwaysCheckDefault2.accesskey "y">
|
||||
<!ENTITY setAsMyDefaultBrowser2.label "Make Default">
|
||||
<!ENTITY setAsMyDefaultBrowser2.accesskey "D">
|
||||
<!ENTITY isDefault.label "&brandShortName; is currently your default browser">
|
||||
|
@ -2,8 +2,11 @@
|
||||
- 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/. -->
|
||||
|
||||
<!ENTITY ctrlTabRecentlyUsedOrder.label "Ctrl+Tab cycles through tabs in recently used order">
|
||||
<!ENTITY ctrlTabRecentlyUsedOrder.accesskey "T">
|
||||
|
||||
<!ENTITY newWindowsAsTabs.label "Open new windows in a new tab instead">
|
||||
<!ENTITY newWindowsAsTabs.accesskey "t">
|
||||
<!ENTITY newWindowsAsTabs.accesskey "w">
|
||||
|
||||
<!ENTITY warnCloseMultipleTabs.label "Warn me when closing multiple tabs">
|
||||
<!ENTITY warnCloseMultipleTabs.accesskey "m">
|
||||
|
@ -91,10 +91,6 @@
|
||||
|
||||
/* Use smaller back button icon */
|
||||
@media (min-resolution: 2dppx) {
|
||||
#back-button {
|
||||
-moz-image-region: rect(0, 108px, 36px, 72px);
|
||||
}
|
||||
|
||||
#back-button:hover:active:not([disabled="true"]) {
|
||||
-moz-image-region: rect(36px, 108px, 72px, 72px);
|
||||
}
|
||||
|
@ -362,8 +362,8 @@ description#identity-popup-content-verifier,
|
||||
margin-inline-start: calc(-1em - 16px);
|
||||
}
|
||||
|
||||
#identity-popup-permission-list menulist {
|
||||
min-width: 60px;
|
||||
.identity-popup-permission-item {
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
#identity-popup-permission-list:not(:empty) {
|
||||
@ -381,5 +381,48 @@ description#identity-popup-content-verifier,
|
||||
|
||||
.identity-popup-permission-label {
|
||||
margin-inline-start: 1em;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.identity-popup-permission-state-label {
|
||||
text-align: end;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.identity-popup-permission-remove-button {
|
||||
-moz-appearance: none;
|
||||
margin: 0;
|
||||
border-width: 0;
|
||||
border-radius: 50%;
|
||||
min-width: 0;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.identity-popup-permission-remove-button > .button-box {
|
||||
border-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.identity-popup-permission-remove-button > .button-box > .button-icon {
|
||||
margin: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
list-style-image: url(chrome://browser/skin/panel-icons.svg#cancel);
|
||||
filter: url(chrome://browser/skin/filters.svg#fill);
|
||||
fill: #999;
|
||||
}
|
||||
|
||||
.identity-popup-permission-remove-button > .button-box > .button-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.identity-popup-permission-remove-button:hover {
|
||||
background-color: #999;
|
||||
}
|
||||
|
||||
.identity-popup-permission-remove-button:hover > .button-box > .button-icon {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.identity-popup-permission-remove-button:hover:active {
|
||||
background-color: #808080;
|
||||
}
|
||||
|
@ -277,6 +277,10 @@ panelmultiview[nosubviews=true] > .panel-viewcontainer > .panel-viewstack > .pan
|
||||
border-radius: 3.5px;
|
||||
}
|
||||
|
||||
panelview[id^=PanelUI-webext-] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
panelview:not([mainview]) .toolbarbutton-text,
|
||||
.cui-widget-panel toolbarbutton > .toolbarbutton-text {
|
||||
text-align: start;
|
||||
|
@ -228,6 +228,12 @@ window:not([chromehidden~="toolbar"]) #urlbar-wrapper {
|
||||
-moz-image-region: rect(0, 54px, 18px, 36px);
|
||||
}
|
||||
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
#back-button {
|
||||
-moz-image-region: rect(0, 108px, 36px, 72px);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-background {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
@ -72,6 +72,7 @@
|
||||
skin/classic/browser/newtab/close.png (../shared/newtab/close.png)
|
||||
skin/classic/browser/newtab/controls.svg (../shared/newtab/controls.svg)
|
||||
skin/classic/browser/newtab/whimsycorn.png (../shared/newtab/whimsycorn.png)
|
||||
skin/classic/browser/panel-icons.svg (../shared/panel-icons.svg)
|
||||
skin/classic/browser/preferences/in-content/favicon.ico (../shared/incontentprefs/favicon.ico)
|
||||
skin/classic/browser/preferences/in-content/icons.svg (../shared/incontentprefs/icons.svg)
|
||||
skin/classic/browser/preferences/in-content/search.css (../shared/incontentprefs/search.css)
|
||||
|
@ -41,7 +41,7 @@
|
||||
margin-inline-end: 10px;
|
||||
}
|
||||
|
||||
#notification-popup-box > .notification-anchor-icon:hover {
|
||||
#notification-popup-box > .notification-anchor-icon:not(.in-use):hover {
|
||||
fill: #606060;
|
||||
}
|
||||
|
||||
@ -274,7 +274,7 @@
|
||||
|
||||
.plugin-icon.plugin-blocked {
|
||||
list-style-image: url(chrome://browser/skin/notification-icons.svg#plugin-blocked);
|
||||
fill: #d92215;
|
||||
fill: #d92215 !important; /* important! to override the default hover color */
|
||||
}
|
||||
|
||||
#notification-popup-box[hidden] {
|
||||
|
8
browser/themes/shared/panel-icons.svg
Normal file
8
browser/themes/shared/panel-icons.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<?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/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
width="32" height="32" viewBox="0 0 32 32">
|
||||
<path id="cancel" d="m 6,9.5 6.5,6.5 -6.5,6.5 3.5,3.5 6.5,-6.5 6.5,6.5 3.5,-3.5 -6.5,-6.5 6.5,-6.5 -3.5,-3.5 -6.5,6.5 -6.5,-6.5 z" />
|
||||
</svg>
|
After Width: | Height: | Size: 466 B |
@ -43,7 +43,6 @@
|
||||
#forward-button > .toolbarbutton-icon {
|
||||
background: var(--chrome-nav-buttons-background) !important;
|
||||
border-radius: 0 !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
padding: var(--toolbarbutton-vertical-inner-padding) 5px !important;
|
||||
margin: 0 !important;
|
||||
@ -51,30 +50,21 @@
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
#back-button > .toolbarbutton-icon {
|
||||
/* 18px icon + 2 * 5px padding + 2 * 1px border */
|
||||
width: 30px !important;
|
||||
}
|
||||
|
||||
#forward-button > .toolbarbutton-icon {
|
||||
/* 18px icon + 2 * 5px padding + 1 * 1px border */
|
||||
width: 29px !important;
|
||||
}
|
||||
|
||||
/* the normal theme adds box-shadow: <stuff> !important when the back-button is [open]. Fix: */
|
||||
#back-button[open="true"] > .toolbarbutton-icon {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Force 1x image for back/forward button for now, otherwise it breaks the
|
||||
layout - Bug 1165360. */
|
||||
@media (min-resolution: 1.1dppx) {
|
||||
#back-button,
|
||||
#forward-button {
|
||||
list-style-image: url("chrome://browser/skin/Toolbar.png");
|
||||
}
|
||||
|
||||
toolbar[brighttext] #back-button,
|
||||
toolbar[brighttext] #forward-button {
|
||||
list-style-image: url("chrome://browser/skin/Toolbar-inverted.png");
|
||||
}
|
||||
|
||||
/* The back button region is already set in devedition.inc.css */
|
||||
#forward-button {
|
||||
-moz-image-region: rect(0px, 72px, 18px, 54px);
|
||||
}
|
||||
}
|
||||
|
||||
#forward-button > .toolbarbutton-icon {
|
||||
border-inline-start: none !important;
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "test-devtools-webextension-nobg",
|
||||
"version": "1.0",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "test-devtools-webextension-nobg@mozilla.org"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/* 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";
|
||||
|
||||
// This function are called from the webconsole test:
|
||||
// browser_addons_debug_webextension.js
|
||||
|
||||
function myWebExtensionAddonFunction() { // eslint-disable-line no-unused-vars
|
||||
console.log("Background page function called", browser.runtime.getManifest());
|
||||
}
|
||||
|
||||
function myWebExtensionShowPopup() { // eslint-disable-line no-unused-vars
|
||||
console.log("readyForOpenPopup");
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<script src="popup.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
Background Page Body Test Content
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,13 @@
|
||||
/* 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 webconsole test:
|
||||
// browser_addons_debug_webextension.js
|
||||
function myWebExtensionPopupAddonFunction() { // eslint-disable-line no-unused-vars
|
||||
console.log("Popup page function called", browser.runtime.getManifest());
|
||||
}
|
@ -7,6 +7,8 @@ support-files =
|
||||
addons/unpacked/install.rdf
|
||||
addons/bad/manifest.json
|
||||
addons/bug1273184.xpi
|
||||
addons/test-devtools-webextension/*
|
||||
addons/test-devtools-webextension-nobg/*
|
||||
service-workers/empty-sw.html
|
||||
service-workers/empty-sw.js
|
||||
service-workers/push-sw.html
|
||||
@ -14,6 +16,10 @@ support-files =
|
||||
!/devtools/client/framework/test/shared-head.js
|
||||
|
||||
[browser_addons_debug_bootstrapped.js]
|
||||
[browser_addons_debug_webextension.js]
|
||||
[browser_addons_debug_webextension_inspector.js]
|
||||
[browser_addons_debug_webextension_nobg.js]
|
||||
[browser_addons_debug_webextension_popup.js]
|
||||
[browser_addons_debugging_initial_state.js]
|
||||
[browser_addons_install.js]
|
||||
[browser_addons_reload.js]
|
||||
|
@ -26,8 +26,11 @@ add_task(function* () {
|
||||
|
||||
let { tab, document } = yield openAboutDebugging("addons");
|
||||
yield waitForInitialAddonList(document);
|
||||
yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
|
||||
"test-devtools");
|
||||
yield installAddon({
|
||||
document,
|
||||
path: "addons/unpacked/install.rdf",
|
||||
name: ADDON_NAME,
|
||||
});
|
||||
|
||||
// Retrieve the DEBUG button for the addon
|
||||
let names = [...document.querySelectorAll("#addons .target-name")];
|
||||
@ -75,6 +78,6 @@ add_task(function* () {
|
||||
yield onToolboxClose;
|
||||
ok(true, "Addon toolbox closed");
|
||||
|
||||
yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
|
||||
yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
|
||||
yield closeAboutDebugging(tab);
|
||||
});
|
||||
|
@ -0,0 +1,74 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// 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
|
||||
} = Cu.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(function* testWebExtensionsToolboxWebConsole() {
|
||||
let {
|
||||
tab, document, debugBtn,
|
||||
} = yield setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_MANIFEST_PATH);
|
||||
|
||||
// Wait for a notification sent by a script evaluated the test addon via
|
||||
// the web console.
|
||||
let onCustomMessage = new Promise(done => {
|
||||
Services.obs.addObserver(function listener(message, topic) {
|
||||
let apiMessage = message.wrappedJSObject;
|
||||
if (!apiMessage.originAttributes ||
|
||||
apiMessage.originAttributes.addonId != ADDON_ID) {
|
||||
return;
|
||||
}
|
||||
Services.obs.removeObserver(listener, "console-api-log-event");
|
||||
done(apiMessage.arguments);
|
||||
}, "console-api-log-event", false);
|
||||
});
|
||||
|
||||
// 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!
|
||||
let env = Cc["@mozilla.org/process/environment;1"]
|
||||
.getService(Ci.nsIEnvironment);
|
||||
let testScript = function () {
|
||||
/* eslint-disable no-undef */
|
||||
toolbox.selectTool("webconsole")
|
||||
.then(console => {
|
||||
let { jsterm } = console.hud;
|
||||
return jsterm.execute("myWebExtensionAddonFunction()");
|
||||
})
|
||||
.then(() => toolbox.destroy());
|
||||
/* eslint-enable no-undef */
|
||||
};
|
||||
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
|
||||
registerCleanupFunction(() => {
|
||||
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
|
||||
});
|
||||
|
||||
let onToolboxClose = BrowserToolboxProcess.once("close");
|
||||
|
||||
debugBtn.click();
|
||||
|
||||
let args = yield onCustomMessage;
|
||||
ok(true, "Received console message from the background page function as expected");
|
||||
is(args[0], "Background page function called", "Got the expected console message");
|
||||
is(args[1] && args[1].name, ADDON_NAME,
|
||||
"Got the expected manifest from WebExtension API");
|
||||
|
||||
yield onToolboxClose;
|
||||
ok(true, "Addon toolbox closed");
|
||||
|
||||
yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
|
||||
yield closeAboutDebugging(tab);
|
||||
});
|
@ -0,0 +1,82 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// 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
|
||||
} = Cu.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(function* testWebExtensionsToolboxInspector() {
|
||||
let {
|
||||
tab, document, debugBtn,
|
||||
} = yield setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_PATH);
|
||||
|
||||
// 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!
|
||||
let env = Cc["@mozilla.org/process/environment;1"]
|
||||
.getService(Ci.nsIEnvironment);
|
||||
let 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");
|
||||
}
|
||||
|
||||
dump("Got a nodeActor\n");
|
||||
|
||||
if (!(nodeActor.inlineTextChild)) {
|
||||
throw new Error("inlineTextChild not found");
|
||||
}
|
||||
|
||||
dump("Got a nodeActor with an inline text child\n");
|
||||
|
||||
let expectedValue = "Background Page Body Test Content";
|
||||
let actualValue = nodeActor.inlineTextChild._form.nodeValue;
|
||||
|
||||
if (String(actualValue).trim() !== String(expectedValue).trim()) {
|
||||
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");
|
||||
});
|
||||
/* eslint-enable no-undef */
|
||||
};
|
||||
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
|
||||
registerCleanupFunction(() => {
|
||||
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
|
||||
});
|
||||
|
||||
let onToolboxClose = BrowserToolboxProcess.once("close");
|
||||
debugBtn.click();
|
||||
yield onToolboxClose;
|
||||
|
||||
ok(true, "Addon toolbox closed");
|
||||
|
||||
yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
|
||||
yield closeAboutDebugging(tab);
|
||||
});
|
@ -0,0 +1,84 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// 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
|
||||
} = Cu.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(function* testWebExtensionsToolboxNoBackgroundPage() {
|
||||
let {
|
||||
tab, document, debugBtn,
|
||||
} = yield setupTestAboutDebuggingWebExtension(ADDON_NOBG_NAME, ADDON_NOBG_PATH);
|
||||
|
||||
// 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!
|
||||
let env = Cc["@mozilla.org/process/environment;1"]
|
||||
.getService(Ci.nsIEnvironment);
|
||||
let 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");
|
||||
}
|
||||
|
||||
dump("Got a nodeActor\n");
|
||||
|
||||
if (!(nodeActor.inlineTextChild)) {
|
||||
throw new Error("inlineTextChild not found");
|
||||
}
|
||||
|
||||
dump("Got a nodeActor with an inline text child\n");
|
||||
|
||||
let expectedValue = "Your addon does not have any document opened yet.";
|
||||
let actualValue = nodeActor.inlineTextChild._form.nodeValue;
|
||||
|
||||
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");
|
||||
});
|
||||
/* eslint-enable no-undef */
|
||||
};
|
||||
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
|
||||
registerCleanupFunction(() => {
|
||||
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
|
||||
});
|
||||
|
||||
let onToolboxClose = BrowserToolboxProcess.once("close");
|
||||
debugBtn.click();
|
||||
yield onToolboxClose;
|
||||
|
||||
ok(true, "Addon toolbox closed");
|
||||
|
||||
yield uninstallAddon({document, id: ADDON_NOBG_ID, name: ADDON_NOBG_NAME});
|
||||
yield closeAboutDebugging(tab);
|
||||
});
|
@ -0,0 +1,180 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// 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
|
||||
} = Cu.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;
|
||||
* - the webextension developer toolbox has a working Inspector panel, with the
|
||||
* background page as default target;
|
||||
* - 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);
|
||||
* - the webextension developer toolbox has a frame list menu and the noautohide toolbar
|
||||
* toggle button, and they can be used to switch the current target to the extension
|
||||
* popup page.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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(function* testWebExtensionsToolboxSwitchToPopup() {
|
||||
let {
|
||||
tab, document, debugBtn,
|
||||
} = yield setupTestAboutDebuggingWebExtension(ADDON_NAME, ADDON_MANIFEST_PATH);
|
||||
|
||||
let onReadyForOpenPopup = new Promise(done => {
|
||||
Services.obs.addObserver(function listener(message, topic) {
|
||||
let apiMessage = message.wrappedJSObject;
|
||||
if (!apiMessage.originAttributes ||
|
||||
apiMessage.originAttributes.addonId != ADDON_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (apiMessage.arguments[0] == "readyForOpenPopup") {
|
||||
Services.obs.removeObserver(listener, "console-api-log-event");
|
||||
done();
|
||||
}
|
||||
}, "console-api-log-event", false);
|
||||
});
|
||||
|
||||
// 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!
|
||||
let env = Cc["@mozilla.org/process/environment;1"]
|
||||
.getService(Ci.nsIEnvironment);
|
||||
let testScript = function () {
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
let jsterm;
|
||||
|
||||
toolbox.selectTool("webconsole")
|
||||
.then(console => {
|
||||
dump(`Clicking the noautohide button\n`);
|
||||
toolbox.doc.getElementById("command-button-noautohide").click();
|
||||
dump(`Clicked the noautohide button\n`);
|
||||
|
||||
let waitForFrameListUpdate = new Promise((done) => {
|
||||
toolbox.target.once("frame-update", () => {
|
||||
done(console);
|
||||
});
|
||||
});
|
||||
|
||||
jsterm = console.hud.jsterm;
|
||||
jsterm.execute("myWebExtensionShowPopup()");
|
||||
|
||||
// Wait the initial frame update (which list the background page).
|
||||
return waitForFrameListUpdate;
|
||||
})
|
||||
.then((console) => {
|
||||
// Wait the new frame update (once the extension popup has been opened).
|
||||
return new Promise((done) => {
|
||||
toolbox.target.once("frame-update", done);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
dump(`Clicking the frame list button\n`);
|
||||
let btn = toolbox.doc.getElementById("command-button-frames");
|
||||
let menu = toolbox.showFramesMenu({target: btn});
|
||||
dump(`Clicked the frame list button\n`);
|
||||
return menu.once("open").then(() => {
|
||||
return menu;
|
||||
});
|
||||
})
|
||||
.then(frameMenu => {
|
||||
let frames = frameMenu.items;
|
||||
|
||||
if (frames.length != 2) {
|
||||
throw Error(`Number of frames found is wrong: ${frames.length} != 2`);
|
||||
}
|
||||
|
||||
let popupFrameBtn = frames.filter((frame) => {
|
||||
return frame.label.endsWith("popup.html");
|
||||
}).pop();
|
||||
|
||||
if (!popupFrameBtn) {
|
||||
throw Error("Extension Popup frame not found in the listed frames");
|
||||
}
|
||||
|
||||
let waitForNavigated = toolbox.target.once("navigate");
|
||||
|
||||
popupFrameBtn.click();
|
||||
|
||||
return waitForNavigated;
|
||||
})
|
||||
.then(() => {
|
||||
return jsterm.execute("myWebExtensionPopupAddonFunction()");
|
||||
})
|
||||
.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");
|
||||
});
|
||||
/* eslint-enable no-undef */
|
||||
};
|
||||
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
|
||||
registerCleanupFunction(() => {
|
||||
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
|
||||
});
|
||||
|
||||
// Wait for a notification sent by a script evaluated the test addon via
|
||||
// the web console.
|
||||
let onPopupCustomMessage = new Promise(done => {
|
||||
Services.obs.addObserver(function listener(message, topic) {
|
||||
let apiMessage = message.wrappedJSObject;
|
||||
if (!apiMessage.originAttributes ||
|
||||
apiMessage.originAttributes.addonId != ADDON_ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (apiMessage.arguments[0] == "Popup page function called") {
|
||||
Services.obs.removeObserver(listener, "console-api-log-event");
|
||||
done(apiMessage.arguments);
|
||||
}
|
||||
}, "console-api-log-event", false);
|
||||
});
|
||||
|
||||
let onToolboxClose = BrowserToolboxProcess.once("close");
|
||||
|
||||
debugBtn.click();
|
||||
|
||||
yield onReadyForOpenPopup;
|
||||
|
||||
let browserActionId = makeWidgetId(ADDON_ID) + "-browser-action";
|
||||
let browserActionEl = window.document.getElementById(browserActionId);
|
||||
|
||||
ok(browserActionEl, "Got the browserAction button from the browser UI");
|
||||
browserActionEl.click();
|
||||
info("Clicked on the browserAction button");
|
||||
|
||||
let args = yield onPopupCustomMessage;
|
||||
ok(true, "Received console message from the popup page function as expected");
|
||||
is(args[0], "Popup page function called", "Got the expected console message");
|
||||
is(args[1] && args[1].name, ADDON_NAME,
|
||||
"Got the expected manifest from WebExtension API");
|
||||
|
||||
yield onToolboxClose;
|
||||
|
||||
ok(true, "Addon toolbox closed");
|
||||
|
||||
yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
|
||||
yield closeAboutDebugging(tab);
|
||||
});
|
@ -50,8 +50,11 @@ function* testCheckboxState(testData) {
|
||||
yield waitForInitialAddonList(document);
|
||||
|
||||
info("Install a test addon.");
|
||||
yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
|
||||
"test-devtools");
|
||||
yield installAddon({
|
||||
document,
|
||||
path: "addons/unpacked/install.rdf",
|
||||
name: ADDON_NAME,
|
||||
});
|
||||
|
||||
info("Test checkbox checked state.");
|
||||
let addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
|
||||
@ -64,7 +67,7 @@ function* testCheckboxState(testData) {
|
||||
"Debug buttons should be in the expected state");
|
||||
|
||||
info("Uninstall test addon installed earlier.");
|
||||
yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
|
||||
yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
|
||||
|
||||
yield closeAboutDebugging(tab);
|
||||
}
|
||||
|
@ -10,11 +10,14 @@ add_task(function* () {
|
||||
yield waitForInitialAddonList(document);
|
||||
|
||||
// Install this add-on, and verify that it appears in the about:debugging UI
|
||||
yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
|
||||
"test-devtools");
|
||||
yield installAddon({
|
||||
document,
|
||||
path: "addons/unpacked/install.rdf",
|
||||
name: ADDON_NAME,
|
||||
});
|
||||
|
||||
// Install the add-on, and verify that it disappears in the about:debugging UI
|
||||
yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
|
||||
yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
|
||||
|
||||
yield closeAboutDebugging(tab);
|
||||
});
|
||||
|
@ -109,8 +109,11 @@ class TempWebExt {
|
||||
add_task(function* reloadButtonReloadsAddon() {
|
||||
const { tab, document } = yield openAboutDebugging("addons");
|
||||
yield waitForInitialAddonList(document);
|
||||
yield installAddon(document, "addons/unpacked/install.rdf",
|
||||
ADDON_NAME, ADDON_NAME);
|
||||
yield installAddon({
|
||||
document,
|
||||
path: "addons/unpacked/install.rdf",
|
||||
name: ADDON_NAME,
|
||||
});
|
||||
|
||||
const reloadButton = getReloadButton(document, ADDON_NAME);
|
||||
is(reloadButton.disabled, false, "Reload button should not be disabled");
|
||||
|
@ -23,8 +23,11 @@ add_task(function* () {
|
||||
yield waitForInitialAddonList(document);
|
||||
|
||||
info("Install a test addon.");
|
||||
yield installAddon(document, "addons/unpacked/install.rdf", ADDON_NAME,
|
||||
"test-devtools");
|
||||
yield installAddon({
|
||||
document,
|
||||
path: "addons/unpacked/install.rdf",
|
||||
name: ADDON_NAME,
|
||||
});
|
||||
|
||||
let addonDebugCheckbox = document.querySelector("#enable-addon-debugging");
|
||||
ok(!addonDebugCheckbox.checked, "Addons debugging should be disabled.");
|
||||
@ -56,7 +59,7 @@ add_task(function* () {
|
||||
ok(debugButtons.every(b => b.disabled), "Debug buttons should be disabled");
|
||||
|
||||
info("Uninstall addon installed earlier.");
|
||||
yield uninstallAddon(document, ADDON_ID, ADDON_NAME);
|
||||
yield uninstallAddon({document, id: ADDON_ID, name: ADDON_NAME});
|
||||
|
||||
yield closeAboutDebugging(tab);
|
||||
});
|
||||
|
@ -6,7 +6,7 @@
|
||||
installAddon, uninstallAddon, waitForMutation, assertHasTarget,
|
||||
getServiceWorkerList, getTabList, openPanel, waitForInitialAddonList,
|
||||
waitForServiceWorkerRegistered, unregisterServiceWorker,
|
||||
waitForDelayedStartupFinished */
|
||||
waitForDelayedStartupFinished, setupTestAboutDebuggingWebExtension */
|
||||
/* import-globals-from ../../framework/test/shared-head.js */
|
||||
|
||||
"use strict";
|
||||
@ -17,6 +17,8 @@ Services.scriptloader.loadSubScript(
|
||||
this);
|
||||
|
||||
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
|
||||
const { Management } = Cu.import("resource://gre/modules/Extension.jsm", {});
|
||||
|
||||
DevToolsUtils.testing = true;
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
@ -148,7 +150,7 @@ function getTabList(document) {
|
||||
document.querySelector("#tabs.targets");
|
||||
}
|
||||
|
||||
function* installAddon(document, path, name, evt) {
|
||||
function* installAddon({document, path, name, isWebExtension}) {
|
||||
// Mock the file picker to select a test addon
|
||||
let MockFilePicker = SpecialPowers.MockFilePicker;
|
||||
MockFilePicker.init(null);
|
||||
@ -158,14 +160,29 @@ function* installAddon(document, path, name, evt) {
|
||||
let addonList = getAddonList(document);
|
||||
let addonListMutation = waitForMutation(addonList, { childList: true });
|
||||
|
||||
// Wait for a message sent by the addon's bootstrap.js file
|
||||
let onAddonInstalled = new Promise(done => {
|
||||
Services.obs.addObserver(function listener() {
|
||||
Services.obs.removeObserver(listener, evt);
|
||||
let onAddonInstalled;
|
||||
|
||||
done();
|
||||
}, evt, false);
|
||||
});
|
||||
if (isWebExtension) {
|
||||
onAddonInstalled = new Promise(done => {
|
||||
Management.on("startup", function listener(event, extension) {
|
||||
if (extension.name != name) {
|
||||
return;
|
||||
}
|
||||
|
||||
Management.off("startup", listener);
|
||||
done();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Wait for a "test-devtools" message sent by the addon's bootstrap.js file
|
||||
onAddonInstalled = new Promise(done => {
|
||||
Services.obs.addObserver(function listener() {
|
||||
Services.obs.removeObserver(listener, "test-devtools");
|
||||
|
||||
done();
|
||||
}, "test-devtools", false);
|
||||
});
|
||||
}
|
||||
// Trigger the file picker by clicking on the button
|
||||
document.getElementById("load-addon-from-file").click();
|
||||
|
||||
@ -180,13 +197,13 @@ function* installAddon(document, path, name, evt) {
|
||||
"The addon name appears in the list of addons: " + names);
|
||||
}
|
||||
|
||||
function* uninstallAddon(document, addonId, addonName) {
|
||||
function* uninstallAddon({document, id, name}) {
|
||||
let addonList = getAddonList(document);
|
||||
let addonListMutation = waitForMutation(addonList, { childList: true });
|
||||
|
||||
// Now uninstall this addon
|
||||
yield new Promise(done => {
|
||||
AddonManager.getAddonByID(addonId, addon => {
|
||||
AddonManager.getAddonByID(id, addon => {
|
||||
let listener = {
|
||||
onUninstalled: function (uninstalledAddon) {
|
||||
if (uninstalledAddon != addon) {
|
||||
@ -206,7 +223,7 @@ function* uninstallAddon(document, addonId, addonName) {
|
||||
yield addonListMutation;
|
||||
let names = [...addonList.querySelectorAll(".target-name")];
|
||||
names = names.map(element => element.textContent);
|
||||
ok(!names.includes(addonName),
|
||||
ok(!names.includes(name),
|
||||
"After uninstall, the addon name disappears from the list of addons: "
|
||||
+ names);
|
||||
}
|
||||
@ -312,3 +329,41 @@ function waitForDelayedStartupFinished(win) {
|
||||
}, "browser-delayed-startup-finished", false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* open the about:debugging page and install an addon
|
||||
*/
|
||||
function* setupTestAboutDebuggingWebExtension(name, path) {
|
||||
yield new Promise(resolve => {
|
||||
let 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
|
||||
["devtools.browser-toolbox.allow-unsafe-script", true],
|
||||
]};
|
||||
SpecialPowers.pushPrefEnv(options, resolve);
|
||||
});
|
||||
|
||||
let { tab, document } = yield openAboutDebugging("addons");
|
||||
yield waitForInitialAddonList(document);
|
||||
|
||||
yield installAddon({
|
||||
document,
|
||||
path,
|
||||
name,
|
||||
isWebExtension: true,
|
||||
});
|
||||
|
||||
// Retrieve the DEBUG button for the addon
|
||||
let names = [...document.querySelectorAll("#addons .target-name")];
|
||||
let nameEl = names.filter(element => element.textContent === name)[0];
|
||||
ok(name, "Found the addon in the list");
|
||||
let targetElement = nameEl.parentNode.parentNode;
|
||||
let debugBtn = targetElement.querySelector(".debug-button");
|
||||
ok(debugBtn, "Found its debug button");
|
||||
|
||||
return { tab, document, debugBtn };
|
||||
}
|
||||
|
@ -479,7 +479,7 @@ skip-if = e10s && debug
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_sources-webext-contentscript.js]
|
||||
[browser_dbg_split-console-paused-reload.js]
|
||||
skip-if = e10s && debug
|
||||
skip-if = true # Bug 1288348 - previously e10s && debug
|
||||
[browser_dbg_stack-01.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_stack-02.js]
|
||||
|
@ -37,12 +37,31 @@ function* runTests() {
|
||||
info("The breadcrumb received focus.");
|
||||
|
||||
// This is the meat of the test.
|
||||
let result = toolbox.once("webconsole-ready", () => {
|
||||
ok(toolbox.splitConsole, "Split console is shown.");
|
||||
is(dbgWin.gThreadClient.state, "paused", "Execution is still paused.");
|
||||
Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
|
||||
});
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {}, dbgWin);
|
||||
yield result;
|
||||
yield resumeDebuggerThenCloseAndFinish(panel);
|
||||
let jsterm = yield getSplitConsole(toolbox);
|
||||
|
||||
is(dbgWin.gThreadClient.state, "paused", "Execution is still paused.");
|
||||
|
||||
let dbgFrameConsoleEvalResult = yield jsterm.execute("privateVar");
|
||||
|
||||
is(
|
||||
dbgFrameConsoleEvalResult.querySelector(".console-string").textContent,
|
||||
'"privateVarValue"',
|
||||
"Got the expected split console result on paused debugger"
|
||||
);
|
||||
|
||||
yield dbgWin.gThreadClient.resume();
|
||||
|
||||
is(dbgWin.gThreadClient.state, "attached", "Execution is resumed.");
|
||||
|
||||
// Get the last evaluation result adopted by the new debugger.
|
||||
let mainTargetConsoleEvalResult = yield jsterm.execute("$_");
|
||||
|
||||
is(
|
||||
mainTargetConsoleEvalResult.querySelector(".console-string").textContent,
|
||||
'"privateVarValue"',
|
||||
"Got the expected split console log on $_ executed on resumed debugger"
|
||||
);
|
||||
|
||||
Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
|
||||
yield closeDebuggerAndFinish(panel);
|
||||
}
|
||||
|
@ -10,10 +10,12 @@
|
||||
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
function runDebuggerStatement() {
|
||||
debugger;
|
||||
function executeFunction() {
|
||||
let privateVar = { propKey: "privateVarValue" };
|
||||
|
||||
window.foobar = "foobar";
|
||||
}
|
||||
window.foobar = 1;
|
||||
executeFunction();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
@ -89,16 +89,16 @@ function attachThread(toolbox) {
|
||||
});
|
||||
};
|
||||
|
||||
if (target.isAddon) {
|
||||
// Attaching an addon
|
||||
if (target.isTabActor) {
|
||||
// Attaching a tab, a browser process, or a WebExtensions add-on.
|
||||
target.activeTab.attachThread(threadOptions, handleResponse);
|
||||
} else if (target.isAddon) {
|
||||
// Attaching a legacy addon.
|
||||
target.client.attachAddon(actor, res => {
|
||||
target.client.attachThread(res.threadActor, handleResponse);
|
||||
});
|
||||
} else if (target.isTabActor) {
|
||||
// Attaching a normal thread
|
||||
target.activeTab.attachThread(threadOptions, handleResponse);
|
||||
} else {
|
||||
// Attaching the browser debugger
|
||||
} else {
|
||||
// Attaching an old browser debugger or a content process.
|
||||
target.client.attachThread(chromeDebugger, handleResponse);
|
||||
}
|
||||
|
||||
|
@ -348,8 +348,15 @@ TabTarget.prototype = {
|
||||
},
|
||||
|
||||
get isAddon() {
|
||||
return !!(this._form && this._form.actor && (
|
||||
this._form.actor.match(/conn\d+\.addon\d+/) ||
|
||||
this._form.actor.match(/conn\d+\.webExtension\d+/)
|
||||
));
|
||||
},
|
||||
|
||||
get isWebExtension() {
|
||||
return !!(this._form && this._form.actor &&
|
||||
this._form.actor.match(/conn\d+\.addon\d+/));
|
||||
this._form.actor.match(/conn\d+\.webExtension\d+/));
|
||||
},
|
||||
|
||||
get isLocalTab() {
|
||||
|
@ -44,7 +44,11 @@ var connect = Task.async(function*() {
|
||||
if (addonID) {
|
||||
gClient.listAddons(({addons}) => {
|
||||
let addonActor = addons.filter(addon => addon.id === addonID).pop();
|
||||
openToolbox({ form: addonActor, chrome: true, isTabActor: false });
|
||||
openToolbox({
|
||||
form: addonActor,
|
||||
chrome: true,
|
||||
isTabActor: addonActor.isWebExtension ? true : false
|
||||
});
|
||||
});
|
||||
} else {
|
||||
gClient.getProcess().then(aResponse => {
|
||||
|
@ -928,7 +928,7 @@ Toolbox.prototype = {
|
||||
* Add buttons to the UI as specified in the devtools.toolbox.toolbarSpec pref
|
||||
*/
|
||||
_buildButtons: function () {
|
||||
if (!this.target.isAddon) {
|
||||
if (!this.target.isAddon || this.target.isWebExtension) {
|
||||
this._buildPickerButton();
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ define(function (require, exports, module) {
|
||||
* of remote JS object and is used as an input object
|
||||
* for this rep component.
|
||||
*/
|
||||
const Grip = React.createClass({
|
||||
const GripRep = React.createClass({
|
||||
displayName: "Grip",
|
||||
|
||||
propTypes: {
|
||||
@ -119,6 +119,7 @@ define(function (require, exports, module) {
|
||||
object: value,
|
||||
equal: ": ",
|
||||
delim: ", ",
|
||||
defaultRep: Grip
|
||||
})));
|
||||
});
|
||||
|
||||
@ -209,9 +210,11 @@ define(function (require, exports, module) {
|
||||
return (object.preview && object.preview.ownProperties);
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
exports.Grip = {
|
||||
rep: Grip,
|
||||
let Grip = {
|
||||
rep: GripRep,
|
||||
supportsObject: supportsObject
|
||||
};
|
||||
|
||||
// Exports from this module
|
||||
exports.Grip = Grip;
|
||||
});
|
||||
|
@ -7,18 +7,26 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar-toggle::before {
|
||||
.sidebar-toggle:-moz-locale-dir(ltr)::before,
|
||||
.sidebar-toggle.pane-collapsed:-moz-locale-dir(rtl)::before {
|
||||
background-image: var(--theme-pane-collapse-image);
|
||||
}
|
||||
|
||||
.sidebar-toggle.pane-collapsed::before {
|
||||
.sidebar-toggle.pane-collapsed:-moz-locale-dir(ltr)::before,
|
||||
.sidebar-toggle:-moz-locale-dir(rtl)::before {
|
||||
background-image: var(--theme-pane-expand-image);
|
||||
}
|
||||
|
||||
/* Rotate button icon 90deg if the toolbox container is
|
||||
in vertical mode (sidebar displayed under the main panel) */
|
||||
@media (max-width: 700px) {
|
||||
.sidebar-toggle::before {
|
||||
.sidebar-toggle:-moz-locale-dir(ltr)::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* Since RTL swaps the used images, we need to flip them
|
||||
the other way round */
|
||||
.sidebar-toggle:-moz-locale-dir(rtl)::before {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
|
@ -16,10 +16,6 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tabs .tabs-menu-item {
|
||||
float: inline-start;
|
||||
}
|
||||
|
||||
.tabs .tabs-menu-item a {
|
||||
display: block;
|
||||
color: #A9A9A9;
|
||||
@ -50,7 +46,7 @@
|
||||
.theme-dark .tabs .tabs-navigation,
|
||||
.theme-light .tabs .tabs-navigation {
|
||||
border-bottom: 1px solid var(--theme-splitter-color);
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.theme-firebug .tabs .tabs-navigation {
|
||||
|
@ -850,11 +850,11 @@ Heritage.extend(SwatchBasedEditorTooltip.prototype, {
|
||||
inspector.once("color-picked", color => {
|
||||
toolbox.win.focus();
|
||||
this._selectColor(color);
|
||||
this._onEyeDropperDone();
|
||||
});
|
||||
|
||||
inspector.once("color-pick-canceled", () => {
|
||||
this.eyedropperOpen = false;
|
||||
this.activeSwatch = null;
|
||||
this._onEyeDropperDone();
|
||||
});
|
||||
|
||||
this.eyedropperOpen = true;
|
||||
@ -865,6 +865,11 @@ Heritage.extend(SwatchBasedEditorTooltip.prototype, {
|
||||
this.tooltip.emit("eyedropper-opened");
|
||||
},
|
||||
|
||||
_onEyeDropperDone: function () {
|
||||
this.eyedropperOpen = false;
|
||||
this.activeSwatch = null;
|
||||
},
|
||||
|
||||
_colorToRgba: function (color) {
|
||||
color = new colorUtils.CssColor(color);
|
||||
let rgba = color._getRGBATuple();
|
||||
|
@ -134,6 +134,9 @@ BrowserAddonActor.prototype = {
|
||||
|
||||
if (this.attached) {
|
||||
this.onDetach();
|
||||
|
||||
// The BrowserAddonActor is not a TabActor and it has to send
|
||||
// "tabDetached" directly to close the devtools toolbox window.
|
||||
this.conn.send({ from: this.actorID, type: "tabDetached" });
|
||||
}
|
||||
|
||||
|
@ -62,6 +62,7 @@ DevToolsModules(
|
||||
'webaudio.js',
|
||||
'webbrowser.js',
|
||||
'webconsole.js',
|
||||
'webextension.js',
|
||||
'webgl.js',
|
||||
'worker.js',
|
||||
)
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global XPCNativeWrapper */
|
||||
|
||||
var { Ci, Cu } = require("chrome");
|
||||
var Services = require("Services");
|
||||
var { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
@ -23,6 +25,7 @@ loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true)
|
||||
loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/script", true);
|
||||
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
|
||||
loader.lazyRequireGetter(this, "BrowserAddonActor", "devtools/server/actors/addon", true);
|
||||
loader.lazyRequireGetter(this, "WebExtensionActor", "devtools/server/actors/webextension", true);
|
||||
loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker", true);
|
||||
loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActorList", "devtools/server/actors/worker", true);
|
||||
loader.lazyRequireGetter(this, "ProcessActorList", "devtools/server/actors/process", true);
|
||||
@ -558,34 +561,36 @@ BrowserTabList.prototype._listenForEventsIf =
|
||||
* @param aMessageNames array of strings
|
||||
* An array of message names.
|
||||
*/
|
||||
BrowserTabList.prototype._listenForMessagesIf = function (aShouldListen, aGuard, aMessageNames) {
|
||||
if (!aShouldListen !== !this[aGuard]) {
|
||||
let op = aShouldListen ? "addMessageListener" : "removeMessageListener";
|
||||
for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) {
|
||||
for (let name of aMessageNames) {
|
||||
win.messageManager[op](name, this);
|
||||
BrowserTabList.prototype._listenForMessagesIf =
|
||||
function (shouldListen, guard, messageNames) {
|
||||
if (!shouldListen !== !this[guard]) {
|
||||
let op = shouldListen ? "addMessageListener" : "removeMessageListener";
|
||||
for (let win of allAppShellDOMWindows(DebuggerServer.chromeWindowType)) {
|
||||
for (let name of messageNames) {
|
||||
win.messageManager[op](name, this);
|
||||
}
|
||||
}
|
||||
this[guard] = shouldListen;
|
||||
}
|
||||
this[aGuard] = aShouldListen;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Implement nsIMessageListener.
|
||||
*/
|
||||
BrowserTabList.prototype.receiveMessage = DevToolsUtils.makeInfallible(function (message) {
|
||||
let browser = message.target;
|
||||
switch (message.name) {
|
||||
case "DOMTitleChanged": {
|
||||
let actor = this._actorByBrowser.get(browser);
|
||||
if (actor) {
|
||||
this._notifyListChanged();
|
||||
this._checkListening();
|
||||
BrowserTabList.prototype.receiveMessage = DevToolsUtils.makeInfallible(
|
||||
function (message) {
|
||||
let browser = message.target;
|
||||
switch (message.name) {
|
||||
case "DOMTitleChanged": {
|
||||
let actor = this._actorByBrowser.get(browser);
|
||||
if (actor) {
|
||||
this._notifyListChanged();
|
||||
this._checkListening();
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Implement nsIDOMEventListener.
|
||||
@ -889,6 +894,16 @@ function TabActor(connection) {
|
||||
TabActor.prototype = {
|
||||
traits: null,
|
||||
|
||||
// Optional console API listener options (e.g. used by the WebExtensionActor to
|
||||
// filter console messages by addonID), set to an empty (no options) object by default.
|
||||
consoleAPIListenerOptions: {},
|
||||
|
||||
// Optional TabSources filter function (e.g. used by the WebExtensionActor to filter
|
||||
// sources by addonID), allow all sources by default.
|
||||
_allowSource() {
|
||||
return true;
|
||||
},
|
||||
|
||||
get exited() {
|
||||
return this._exited;
|
||||
},
|
||||
@ -1059,7 +1074,7 @@ TabActor.prototype = {
|
||||
|
||||
get sources() {
|
||||
if (!this._sources) {
|
||||
this._sources = new TabSources(this.threadActor);
|
||||
this._sources = new TabSources(this.threadActor, this._allowSource);
|
||||
}
|
||||
return this._sources;
|
||||
},
|
||||
@ -1364,17 +1379,28 @@ TabActor.prototype = {
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.outerWindowID;
|
||||
}
|
||||
|
||||
// Collect the addonID from the document origin attributes.
|
||||
let addonID = window.document.nodePrincipal.originAttributes.addonId;
|
||||
|
||||
return {
|
||||
id: id,
|
||||
id,
|
||||
parentID,
|
||||
addonID,
|
||||
url: window.location.href,
|
||||
title: window.document.title,
|
||||
parentID: parentID
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
_notifyDocShellsUpdate(docshells) {
|
||||
let windows = this._docShellsToWindows(docshells);
|
||||
|
||||
// Do not send the `frameUpdate` event if the windows array is empty.
|
||||
if (windows.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.conn.send({ from: this.actorID,
|
||||
type: "frameUpdate",
|
||||
frames: windows
|
||||
@ -2027,7 +2053,7 @@ TabActor.prototype = {
|
||||
// We are very explicitly examining the "console" property of
|
||||
// the non-Xrayed object here.
|
||||
let console = window.wrappedJSObject.console;
|
||||
isNative = new XPCNativeWrapper(console).IS_NATIVE_CONSOLE
|
||||
isNative = new XPCNativeWrapper(console).IS_NATIVE_CONSOLE;
|
||||
} catch (ex) {
|
||||
// ignore
|
||||
}
|
||||
@ -2267,7 +2293,12 @@ BrowserAddonList.prototype.getList = function () {
|
||||
for (let addon of addons) {
|
||||
let actor = this._actorByAddonId.get(addon.id);
|
||||
if (!actor) {
|
||||
actor = new BrowserAddonActor(this._connection, addon);
|
||||
if (addon.isWebExtension) {
|
||||
actor = new WebExtensionActor(this._connection, addon);
|
||||
} else {
|
||||
actor = new BrowserAddonActor(this._connection, addon);
|
||||
}
|
||||
|
||||
this._actorByAddonId.set(addon.id, actor);
|
||||
}
|
||||
}
|
||||
@ -2314,12 +2345,10 @@ BrowserAddonList.prototype._adjustListener = function () {
|
||||
// As long as the callback exists, we need to listen for changes
|
||||
// so we can notify about add-on changes.
|
||||
AddonManager.addAddonListener(this);
|
||||
} else {
|
||||
} else if (this._actorByAddonId.size === 0) {
|
||||
// When the callback does not exist, we only need to keep listening
|
||||
// if the actor cache will need adjusting when add-ons change.
|
||||
if (this._actorByAddonId.size === 0) {
|
||||
AddonManager.removeAddonListener(this);
|
||||
}
|
||||
AddonManager.removeAddonListener(this);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -594,8 +594,11 @@ WebConsoleActor.prototype =
|
||||
break;
|
||||
case "ConsoleAPI":
|
||||
if (!this.consoleAPIListener) {
|
||||
// Create the consoleAPIListener (and apply the filtering options defined
|
||||
// in the parent actor).
|
||||
this.consoleAPIListener =
|
||||
new ConsoleAPIListener(window, this);
|
||||
new ConsoleAPIListener(window, this,
|
||||
this.parentActor.consoleAPIListenerOptions);
|
||||
this.consoleAPIListener.init();
|
||||
}
|
||||
startedListeners.push(listener);
|
||||
@ -1296,7 +1299,19 @@ WebConsoleActor.prototype =
|
||||
evalOptions = { url: aOptions.url };
|
||||
}
|
||||
|
||||
// If the debugger object is changed from the last evaluation,
|
||||
// adopt this._lastConsoleInputEvaluation value in the new debugger,
|
||||
// to prevents "Debugger.Object belongs to a different Debugger" exceptions
|
||||
// related to the $_ bindings.
|
||||
if (this._lastConsoleInputEvaluation &&
|
||||
this._lastConsoleInputEvaluation.global !== dbgWindow) {
|
||||
this._lastConsoleInputEvaluation = dbg.adoptDebuggeeValue(
|
||||
this._lastConsoleInputEvaluation
|
||||
);
|
||||
}
|
||||
|
||||
let result;
|
||||
|
||||
if (frame) {
|
||||
result = frame.evalWithBindings(aString, bindings, evalOptions);
|
||||
}
|
||||
|
333
devtools/server/actors/webextension.js
Normal file
333
devtools/server/actors/webextension.js
Normal file
@ -0,0 +1,333 @@
|
||||
/* 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 { Ci, Cu } = require("chrome");
|
||||
const Services = require("Services");
|
||||
const { ChromeActor } = require("./chrome");
|
||||
const makeDebugger = require("./utils/make-debugger");
|
||||
|
||||
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
var { assert } = DevToolsUtils;
|
||||
|
||||
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
|
||||
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
|
||||
|
||||
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
|
||||
loader.lazyImporter(this, "XPIProvider", "resource://gre/modules/addons/XPIProvider.jsm");
|
||||
|
||||
const FALLBACK_DOC_MESSAGE = "Your addon does not have any document opened yet.";
|
||||
|
||||
/**
|
||||
* Creates a TabActor for debugging all the contexts associated to a target WebExtensions
|
||||
* add-on.
|
||||
* Most of the implementation is inherited from ChromeActor (which inherits most of its
|
||||
* implementation from TabActor).
|
||||
* WebExtensionActor is a child of RootActor, it can be retrieved via
|
||||
* RootActor.listAddons request.
|
||||
* WebExtensionActor exposes all tab actors via its form() request, like TabActor.
|
||||
*
|
||||
* History lecture:
|
||||
* The add-on actors used to not inherit TabActor because of the different way the
|
||||
* add-on APIs where exposed to the add-on itself, and for this reason the Addon Debugger
|
||||
* has only a sub-set of the feature available in the Tab or in the Browser Toolbox.
|
||||
* In a WebExtensions add-on all the provided contexts (background and popup pages etc.),
|
||||
* besides the Content Scripts which run in the content process, hooked to an existent
|
||||
* tab, by creating a new WebExtensionActor which inherits from ChromeActor, we can
|
||||
* provide a full features Addon Toolbox (which is basically like a BrowserToolbox which
|
||||
* filters the visible sources and frames to the one that are related to the target
|
||||
* add-on).
|
||||
*
|
||||
* @param conn DebuggerServerConnection
|
||||
* The connection to the client.
|
||||
* @param addon AddonWrapper
|
||||
* The target addon.
|
||||
*/
|
||||
function WebExtensionActor(conn, addon) {
|
||||
ChromeActor.call(this, conn);
|
||||
|
||||
this.id = addon.id;
|
||||
this.addon = addon;
|
||||
|
||||
// Bind the _allowSource helper to this, it is used in the
|
||||
// TabActor to lazily create the TabSources instance.
|
||||
this._allowSource = this._allowSource.bind(this);
|
||||
|
||||
// Set the consoleAPIListener filtering options
|
||||
// (retrieved and used in the related webconsole child actor).
|
||||
this.consoleAPIListenerOptions = {
|
||||
addonId: addon.id,
|
||||
};
|
||||
|
||||
// This creates a Debugger instance for debugging all the add-on globals.
|
||||
this.makeDebugger = makeDebugger.bind(null, {
|
||||
findDebuggees: dbg => {
|
||||
return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee);
|
||||
},
|
||||
shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee.bind(this),
|
||||
});
|
||||
|
||||
// Discover the preferred debug global for the target addon
|
||||
this.preferredTargetWindow = null;
|
||||
this._findAddonPreferredTargetWindow();
|
||||
|
||||
AddonManager.addAddonListener(this);
|
||||
}
|
||||
exports.WebExtensionActor = WebExtensionActor;
|
||||
|
||||
WebExtensionActor.prototype = Object.create(ChromeActor.prototype);
|
||||
|
||||
WebExtensionActor.prototype.actorPrefix = "webExtension";
|
||||
WebExtensionActor.prototype.constructor = WebExtensionActor;
|
||||
|
||||
// NOTE: This is needed to catch in the webextension webconsole all the
|
||||
// errors raised by the WebExtension internals that are not currently
|
||||
// associated with any window.
|
||||
WebExtensionActor.prototype.isRootActor = true;
|
||||
|
||||
WebExtensionActor.prototype.form = function () {
|
||||
assert(this.actorID, "addon should have an actorID.");
|
||||
|
||||
let baseForm = ChromeActor.prototype.form.call(this);
|
||||
|
||||
return Object.assign(baseForm, {
|
||||
actor: this.actorID,
|
||||
id: this.id,
|
||||
name: this.addon.name,
|
||||
url: this.addon.sourceURI ? this.addon.sourceURI.spec : undefined,
|
||||
iconURL: this.addon.iconURL,
|
||||
debuggable: this.addon.isDebuggable,
|
||||
temporarilyInstalled: this.addon.temporarilyInstalled,
|
||||
isWebExtension: this.addon.isWebExtension,
|
||||
});
|
||||
};
|
||||
|
||||
WebExtensionActor.prototype._attach = function () {
|
||||
// NOTE: we need to be sure that `this.window` can return a
|
||||
// window before calling the ChromeActor.onAttach, or the TabActor
|
||||
// will not be subscribed to the child doc shell updates.
|
||||
|
||||
// If a preferredTargetWindow exists, set it as the target for this actor
|
||||
// when the client request to attach this actor.
|
||||
if (this.preferredTargetWindow) {
|
||||
this._setWindow(this.preferredTargetWindow);
|
||||
} else {
|
||||
this._createFallbackWindow();
|
||||
}
|
||||
|
||||
// Call ChromeActor's _attach to listen for any new/destroyed chrome docshell
|
||||
ChromeActor.prototype._attach.apply(this);
|
||||
};
|
||||
|
||||
WebExtensionActor.prototype._detach = function () {
|
||||
this._destroyFallbackWindow();
|
||||
|
||||
// Call ChromeActor's _detach to unsubscribe new/destroyed chrome docshell listeners.
|
||||
ChromeActor.prototype._detach.apply(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the actor is removed from the connection.
|
||||
*/
|
||||
WebExtensionActor.prototype.exit = function () {
|
||||
AddonManager.removeAddonListener(this);
|
||||
|
||||
this.preferredTargetWindow = null;
|
||||
this.addon = null;
|
||||
this.id = null;
|
||||
|
||||
return ChromeActor.prototype.exit.apply(this);
|
||||
};
|
||||
|
||||
// Addon Specific Remote Debugging requestTypes and methods.
|
||||
|
||||
/**
|
||||
* Reloads the addon.
|
||||
*/
|
||||
WebExtensionActor.prototype.onReload = function () {
|
||||
return this.addon.reload()
|
||||
.then(() => {
|
||||
// send an empty response
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the preferred global for the add-on (called from the AddonManager).
|
||||
*/
|
||||
WebExtensionActor.prototype.setOptions = function (addonOptions) {
|
||||
if ("global" in addonOptions) {
|
||||
// Set the proposed debug global as the preferred target window
|
||||
// (the actor will eventually set it as the target once it is attached)
|
||||
this.preferredTargetWindow = addonOptions.global;
|
||||
}
|
||||
};
|
||||
|
||||
// AddonManagerListener callbacks.
|
||||
|
||||
WebExtensionActor.prototype.onInstalled = function (addon) {
|
||||
if (addon.id != this.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the AddonManager's addon object on reload/update.
|
||||
this.addon = addon;
|
||||
};
|
||||
|
||||
WebExtensionActor.prototype.onUninstalled = function (addon) {
|
||||
if (addon != this.addon) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.exit();
|
||||
};
|
||||
|
||||
WebExtensionActor.prototype.onPropertyChanged = function (addon, changedPropNames) {
|
||||
if (addon != this.addon) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh the preferred debug global on disabled/reloaded/upgraded addon.
|
||||
if (changedPropNames.includes("debugGlobal")) {
|
||||
this._findAddonPreferredTargetWindow();
|
||||
}
|
||||
};
|
||||
|
||||
// Private helpers
|
||||
|
||||
WebExtensionActor.prototype._createFallbackWindow = function () {
|
||||
if (this.fallbackWindow) {
|
||||
// Skip if there is already an existent fallback window.
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an empty hidden window as a fallback (e.g. the background page could be
|
||||
// not defined for the target add-on or not yet when the actor instance has been
|
||||
// created).
|
||||
this.fallbackWebNav = Services.appShell.createWindowlessBrowser(true);
|
||||
this.fallbackWebNav.loadURI(
|
||||
`data:text/html;charset=utf-8,${FALLBACK_DOC_MESSAGE}`,
|
||||
0, null, null, null
|
||||
);
|
||||
|
||||
this.fallbackDocShell = this.fallbackWebNav
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell);
|
||||
|
||||
Object.defineProperty(this, "docShell", {
|
||||
value: this.fallbackDocShell,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
// Save the reference to the fallback DOMWindow
|
||||
this.fallbackWindow = this.fallbackDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow);
|
||||
};
|
||||
|
||||
WebExtensionActor.prototype._destroyFallbackWindow = function () {
|
||||
if (this.fallbackWebNav) {
|
||||
// Explicitly close the fallback windowless browser to prevent it to leak
|
||||
// (and to prevent it to freeze devtools xpcshell tests).
|
||||
this.fallbackWebNav.loadURI("about:blank", 0, null, null, null);
|
||||
this.fallbackWebNav.close();
|
||||
|
||||
this.fallbackWebNav = null;
|
||||
this.fallbackWindow = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Discover the preferred debug global and switch to it if the addon has been attached.
|
||||
*/
|
||||
WebExtensionActor.prototype._findAddonPreferredTargetWindow = function () {
|
||||
return new Promise(resolve => {
|
||||
let activeAddon = XPIProvider.activeAddons.get(this.id);
|
||||
|
||||
if (!activeAddon) {
|
||||
// The addon is not active, the background page is going to be destroyed,
|
||||
// navigate to the fallback window (if it already exists).
|
||||
resolve(null);
|
||||
} else {
|
||||
AddonManager.getAddonByInstanceID(activeAddon.instanceID)
|
||||
.then(privateWrapper => {
|
||||
let targetWindow = privateWrapper.getDebugGlobal();
|
||||
|
||||
// Do not use the preferred global if it is not a DOMWindow as expected.
|
||||
if (!(targetWindow instanceof Ci.nsIDOMWindow)) {
|
||||
targetWindow = null;
|
||||
}
|
||||
|
||||
resolve(targetWindow);
|
||||
});
|
||||
}
|
||||
}).then(preferredTargetWindow => {
|
||||
this.preferredTargetWindow = preferredTargetWindow;
|
||||
|
||||
if (!preferredTargetWindow) {
|
||||
// Create a fallback window if no preferred target window has been found.
|
||||
this._createFallbackWindow();
|
||||
} else if (this.attached) {
|
||||
// Change the top level document if the actor is already attached.
|
||||
this._changeTopLevelDocument(preferredTargetWindow);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return an array of the json details related to an array/iterator of docShells.
|
||||
*/
|
||||
WebExtensionActor.prototype._docShellsToWindows = function (docshells) {
|
||||
return ChromeActor.prototype._docShellsToWindows.call(this, docshells)
|
||||
.filter(windowDetails => {
|
||||
// filter the docShells based on the addon id
|
||||
return windowDetails.addonID == this.id;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true if the given source is associated with this addon and should be
|
||||
* added to the visible sources (retrieved and used by the webbrowser actor module).
|
||||
*/
|
||||
WebExtensionActor.prototype._allowSource = function (source) {
|
||||
try {
|
||||
let uri = Services.io.newURI(source.url, null, null);
|
||||
let addonID = mapURIToAddonID(uri);
|
||||
|
||||
return addonID == this.id;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true if the given global is associated with this addon and should be
|
||||
* added as a debuggee, false otherwise.
|
||||
*/
|
||||
WebExtensionActor.prototype._shouldAddNewGlobalAsDebuggee = function (newGlobal) {
|
||||
const global = unwrapDebuggerObjectGlobal(newGlobal);
|
||||
|
||||
if (global instanceof Ci.nsIDOMWindow) {
|
||||
return global.document.nodePrincipal.originAttributes.addonId == this.id;
|
||||
}
|
||||
|
||||
try {
|
||||
// This will fail for non-Sandbox objects, hence the try-catch block.
|
||||
let metadata = Cu.getSandboxMetadata(global);
|
||||
if (metadata) {
|
||||
return metadata.addonID === this.id;
|
||||
}
|
||||
} catch (e) {
|
||||
// Unable to retrieve the sandbox metadata.
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override WebExtensionActor requestTypes:
|
||||
* - redefined `reload`, which should reload the target addon
|
||||
* (instead of the entire browser as the regular ChromeActor does).
|
||||
*/
|
||||
WebExtensionActor.prototype.requestTypes.reload = WebExtensionActor.prototype.onReload;
|
@ -4569,7 +4569,6 @@ pref("gfx.apitrace.enabled",false);
|
||||
pref("gfx.content.use-native-pushlayer", true);
|
||||
#ifdef MOZ_WIDGET_GTK
|
||||
pref("gfx.xrender.enabled",false);
|
||||
pref("widget.allow-gtk-dark-theme", false);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# %1: the user name (Ed), %2: the app name (Firefox), %3: the operating system (Android)
|
||||
client.name2 = %1$S's %2$S on %3$S
|
||||
client.name2 = %1$S’s %2$S on %3$S
|
||||
|
||||
# %S is the date and time at which the last sync successfully completed
|
||||
lastSync2.label = Last sync: %S
|
||||
|
@ -434,7 +434,8 @@ AddonsReconciler.prototype = {
|
||||
modified: now,
|
||||
type: addon.type,
|
||||
scope: addon.scope,
|
||||
foreignInstall: addon.foreignInstall
|
||||
foreignInstall: addon.foreignInstall,
|
||||
isSyncable: addon.isSyncable,
|
||||
};
|
||||
this._addons[id] = record;
|
||||
this._log.debug("Adding change because add-on not present locally: " +
|
||||
|
@ -374,6 +374,9 @@ AddonUtilsInternal.prototype = {
|
||||
// Verify that the source URI uses TLS. We don't allow installs from
|
||||
// insecure sources for security reasons. The Addon Manager ensures
|
||||
// that cert validation etc is performed.
|
||||
// (We should also consider just dropping this entirely and calling
|
||||
// XPIProvider.isInstallAllowed, but that has additional semantics we might
|
||||
// need to think through...)
|
||||
let requireSecureURI = true;
|
||||
if (options && options.requireSecureURI !== undefined) {
|
||||
requireSecureURI = options.requireSecureURI;
|
||||
|
@ -25,10 +25,13 @@
|
||||
*
|
||||
* Synchronization is influenced by the following preferences:
|
||||
*
|
||||
* - services.sync.addons.ignoreRepositoryChecking
|
||||
* - services.sync.addons.ignoreUserEnabledChanges
|
||||
* - services.sync.addons.trustedSourceHostnames
|
||||
*
|
||||
* and also influenced by whether addons have repository caching enabled and
|
||||
* whether they allow installation of addons from insecure options (both of
|
||||
* which are themselves influenced by the "extensions." pref branch)
|
||||
*
|
||||
* See the documentation in services-sync.js for the behavior of these prefs.
|
||||
*/
|
||||
"use strict";
|
||||
@ -278,6 +281,14 @@ AddonsStore.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore incoming records for which an existing non-syncable addon
|
||||
// exists.
|
||||
let existingMeta = this.reconciler.addons[record.addonID];
|
||||
if (existingMeta && !this.isAddonSyncable(existingMeta)) {
|
||||
this._log.info("Ignoring incoming record for an existing but non-syncable addon", record.addonID);
|
||||
return;
|
||||
}
|
||||
|
||||
Store.prototype.applyIncoming.call(this, record);
|
||||
},
|
||||
|
||||
@ -291,7 +302,7 @@ AddonsStore.prototype = {
|
||||
id: record.addonID,
|
||||
syncGUID: record.id,
|
||||
enabled: record.enabled,
|
||||
requireSecureURI: !Svc.Prefs.get("addons.ignoreRepositoryChecking", false),
|
||||
requireSecureURI: this._extensionsPrefs.get("install.requireSecureOrigin", true),
|
||||
}], cb);
|
||||
|
||||
// This will throw if there was an error. This will get caught by the sync
|
||||
@ -531,7 +542,10 @@ AddonsStore.prototype = {
|
||||
// 3) Not installed by a foreign entity (i.e. installed by the app)
|
||||
// since they act like global extensions.
|
||||
// 4) Is not a hotfix.
|
||||
// 5) Are installed from AMO
|
||||
// 5) The addons XPIProvider doesn't veto it (i.e not being installed in
|
||||
// the profile directory, or any other reasons it says the addon can't
|
||||
// be synced)
|
||||
// 6) Are installed from AMO
|
||||
|
||||
// We could represent the test as a complex boolean expression. We go the
|
||||
// verbose route so the failure reason is logged.
|
||||
@ -551,6 +565,12 @@ AddonsStore.prototype = {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the addon manager says it's not syncable, we skip it.
|
||||
if (!addon.isSyncable) {
|
||||
this._log.debug(addon.id + " not syncable: vetoed by the addon manager.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// This may be too aggressive. If an add-on is downloaded from AMO and
|
||||
// manually placed in the profile directory, foreignInstall will be set.
|
||||
// Arguably, that add-on should be syncable.
|
||||
@ -561,15 +581,19 @@ AddonsStore.prototype = {
|
||||
}
|
||||
|
||||
// Ignore hotfix extensions (bug 741670). The pref may not be defined.
|
||||
// XXX - note that addon.isSyncable will be false for hotfix addons, so
|
||||
// this check isn't strictly necessary - except for Sync tests which aren't
|
||||
// setup to create a "real" hotfix addon. This can be removed once those
|
||||
// tests are fixed (but keeping it doesn't hurt either)
|
||||
if (this._extensionsPrefs.get("hotfix.id", null) == addon.id) {
|
||||
this._log.debug(addon.id + " not syncable: is a hotfix.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// We provide a back door to skip the repository checking of an add-on.
|
||||
// This is utilized by the tests to make testing easier. Users could enable
|
||||
// this, but it would sacrifice security.
|
||||
if (Svc.Prefs.get("addons.ignoreRepositoryChecking", false)) {
|
||||
// If the AddonRepository's cache isn't enabled (which it typically isn't
|
||||
// in tests), getCachedAddonByID always returns null - so skip the check
|
||||
// in that case.
|
||||
if (!AddonRepository.cacheEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,13 @@ XPCOMUtils.defineLazyModuleGetter(this, "Status",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
// Get the value for an interval that's stored in preferences. To save users
|
||||
// from themselves (and us from them!) the minimum time they can specify
|
||||
// is 60s.
|
||||
function getThrottledIntervalPreference(prefName) {
|
||||
return Math.max(Svc.Prefs.get(prefName), 60) * 1000;
|
||||
}
|
||||
|
||||
this.SyncScheduler = function SyncScheduler(service) {
|
||||
this.service = service;
|
||||
this.init();
|
||||
@ -48,12 +55,12 @@ SyncScheduler.prototype = {
|
||||
|
||||
let part = service.fxAccountsEnabled ? "fxa" : "sync11";
|
||||
let prefSDInterval = "scheduler." + part + ".singleDeviceInterval";
|
||||
this.singleDeviceInterval = Svc.Prefs.get(prefSDInterval) * 1000;
|
||||
this.singleDeviceInterval = getThrottledIntervalPreference(prefSDInterval);
|
||||
|
||||
this.idleInterval = Svc.Prefs.get("scheduler.idleInterval") * 1000;
|
||||
this.activeInterval = Svc.Prefs.get("scheduler.activeInterval") * 1000;
|
||||
this.immediateInterval = Svc.Prefs.get("scheduler.immediateInterval") * 1000;
|
||||
this.eolInterval = Svc.Prefs.get("scheduler.eolInterval") * 1000;
|
||||
this.idleInterval = getThrottledIntervalPreference("scheduler.idleInterval");
|
||||
this.activeInterval = getThrottledIntervalPreference("scheduler.activeInterval");
|
||||
this.immediateInterval = getThrottledIntervalPreference("scheduler.immediateInterval");
|
||||
this.eolInterval = getThrottledIntervalPreference("scheduler.eolInterval");
|
||||
|
||||
// A user is non-idle on startup by default.
|
||||
this.idle = false;
|
||||
|
@ -38,9 +38,6 @@ pref("services.sync.jpake.firstMsgMaxTries", 300); // 5 minutes
|
||||
pref("services.sync.jpake.lastMsgMaxTries", 300); // 5 minutes
|
||||
pref("services.sync.jpake.maxTries", 10);
|
||||
|
||||
// Allow add-ons to be synced from non-trusted sources.
|
||||
pref("services.sync.addons.ignoreRepositoryChecking", false);
|
||||
|
||||
// If true, add-on sync ignores changes to the user-enabled flag. This
|
||||
// allows people to have the same set of add-ons installed across all
|
||||
// profiles while maintaining different enabled states.
|
||||
|
@ -4,6 +4,7 @@
|
||||
Cu.import("resource://services-common/async.js");
|
||||
Cu.import("resource://testing-common/services/common/utils.js");
|
||||
Cu.import("resource://testing-common/PlacesTestUtils.jsm");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, 'SyncPingSchema', function() {
|
||||
@ -390,3 +391,12 @@ function sync_engine_and_validate_telem(engine, allowErrorPings, onError) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Avoid an issue where `client.name2` containing unicode characters causes
|
||||
// a number of tests to fail, due to them assuming that we do not need to utf-8
|
||||
// encode or decode data sent through the mocked server (see bug 1268912).
|
||||
Utils.getDefaultDeviceName = function() {
|
||||
return "Test device name";
|
||||
};
|
||||
|
||||
|
||||
|
27
services/sync/tests/unit/systemaddon-search.xml
Normal file
27
services/sync/tests/unit/systemaddon-search.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<searchresults total_results="1">
|
||||
<addon id="5618">
|
||||
<name>System Add-on Test</name>
|
||||
<type id="1">Extension</type>
|
||||
<guid>system1@tests.mozilla.org</guid>
|
||||
<slug>addon11</slug>
|
||||
<version>1.0</version>
|
||||
|
||||
<compatible_applications><application>
|
||||
<name>Firefox</name>
|
||||
<application_id>1</application_id>
|
||||
<min_version>3.6</min_version>
|
||||
<max_version>*</max_version>
|
||||
<appID>xpcshell@tests.mozilla.org</appID>
|
||||
</application></compatible_applications>
|
||||
<all_compatible_os><os>ALL</os></all_compatible_os>
|
||||
|
||||
<install os="ALL" size="999">http://127.0.0.1:8888/system.xpi</install>
|
||||
<created epoch="1252903662">
|
||||
2009-09-14T04:47:42Z
|
||||
</created>
|
||||
<last_updated epoch="1315255329">
|
||||
2011-09-05T20:42:09Z
|
||||
</last_updated>
|
||||
</addon>
|
||||
</searchresults>
|
@ -103,8 +103,6 @@ add_test(function test_source_uri_rewrite() {
|
||||
// This tests for conformance with bug 708134 so server-side metrics aren't
|
||||
// skewed.
|
||||
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
|
||||
// We resort to monkeypatching because of the API design.
|
||||
let oldFunction = AddonUtils.__proto__.installAddonFromSearchResult;
|
||||
|
||||
@ -139,6 +137,5 @@ add_test(function test_source_uri_rewrite() {
|
||||
do_check_true(installCalled);
|
||||
AddonUtils.__proto__.installAddonFromSearchResult = oldFunction;
|
||||
|
||||
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
|
@ -16,6 +16,7 @@ Cu.import("resource://testing-common/services/sync/utils.js");
|
||||
var prefs = new Preferences();
|
||||
prefs.set("extensions.getAddons.get.url",
|
||||
"http://localhost:8888/search/guid:%IDS%");
|
||||
prefs.set("extensions.install.requireSecureOrigin", false);
|
||||
|
||||
loadAddonTestFunctions();
|
||||
startupManager();
|
||||
@ -35,8 +36,6 @@ function advance_test() {
|
||||
reconciler.saveState(null, cb);
|
||||
cb.wait();
|
||||
|
||||
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
@ -104,7 +103,6 @@ add_test(function test_get_changed_ids() {
|
||||
tracker.clearChangedIDs();
|
||||
|
||||
_("Ensure reconciler changes are populated.");
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
let addon = installAddon("test_bootstrap1_1");
|
||||
tracker.clearChangedIDs(); // Just in case.
|
||||
changes = engine.getChangedIDs();
|
||||
@ -151,9 +149,6 @@ add_test(function test_disabled_install_semantics() {
|
||||
// This is essentially a test for bug 712542, which snuck into the original
|
||||
// add-on sync drop. It ensures that when an add-on is installed that the
|
||||
// disabled state and incoming syncGUID is preserved, even on the next sync.
|
||||
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
|
||||
const USER = "foo";
|
||||
const PASSWORD = "password";
|
||||
const PASSPHRASE = "abcdeabcdeabcdeabcdeabcdea";
|
||||
|
@ -10,13 +10,33 @@ Cu.import("resource://services-sync/engines/addons.js");
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://testing-common/services/sync/utils.js");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
const HTTP_PORT = 8888;
|
||||
|
||||
var prefs = new Preferences();
|
||||
|
||||
prefs.set("extensions.getAddons.get.url", "http://localhost:8888/search/guid:%IDS%");
|
||||
prefs.set("extensions.install.requireSecureOrigin", false);
|
||||
|
||||
const SYSTEM_ADDON_ID = "system1@tests.mozilla.org";
|
||||
let systemAddonFile;
|
||||
|
||||
// The system add-on must be installed before AddonManager is started.
|
||||
function loadSystemAddon() {
|
||||
let addonFilename = SYSTEM_ADDON_ID + ".xpi";
|
||||
const distroDir = FileUtils.getDir("ProfD", ["sysfeatures", "app0"], true);
|
||||
do_get_file(ExtensionsTestPath("/data/system_addons/system1_1.xpi")).copyTo(distroDir, addonFilename);
|
||||
systemAddonFile = FileUtils.File(distroDir.path);
|
||||
systemAddonFile.append(addonFilename);
|
||||
systemAddonFile.lastModifiedTime = Date.now();
|
||||
// As we're not running in application, we need to setup the features directory
|
||||
// used by system add-ons.
|
||||
registerDirectory("XREAppFeat", distroDir);
|
||||
}
|
||||
|
||||
loadAddonTestFunctions();
|
||||
loadSystemAddon();
|
||||
startupManager();
|
||||
|
||||
Service.engineManager.register(AddonsEngine);
|
||||
@ -57,6 +77,10 @@ function createAndStartHTTPServer(port) {
|
||||
server.registerFile("/search/guid:missing-xpi%40tests.mozilla.org",
|
||||
do_get_file("missing-xpi-search.xml"));
|
||||
|
||||
server.registerFile("/search/guid:system1%40tests.mozilla.org",
|
||||
do_get_file("systemaddon-search.xml"));
|
||||
server.registerFile("/system.xpi", systemAddonFile);
|
||||
|
||||
server.start(port);
|
||||
|
||||
return server;
|
||||
@ -70,6 +94,7 @@ function createAndStartHTTPServer(port) {
|
||||
function run_test() {
|
||||
initTestLogging("Trace");
|
||||
Log.repository.getLogger("Sync.Engine.Addons").level = Log.Level.Trace;
|
||||
Log.repository.getLogger("Sync.Tracker.Addons").level = Log.Level.Trace;
|
||||
Log.repository.getLogger("Sync.AddonsRepository").level =
|
||||
Log.Level.Trace;
|
||||
|
||||
@ -194,7 +219,6 @@ add_test(function test_apply_uninstall() {
|
||||
add_test(function test_addon_syncability() {
|
||||
_("Ensure isAddonSyncable functions properly.");
|
||||
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
Svc.Prefs.set("addons.trustedSourceHostnames",
|
||||
"addons.mozilla.org,other.example.com");
|
||||
|
||||
@ -204,7 +228,7 @@ add_test(function test_addon_syncability() {
|
||||
do_check_true(store.isAddonSyncable(addon));
|
||||
|
||||
let dummy = {};
|
||||
const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"];
|
||||
const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall", "isSyncable"];
|
||||
for (let k of KEYS) {
|
||||
dummy[k] = addon[k];
|
||||
}
|
||||
@ -219,6 +243,10 @@ add_test(function test_addon_syncability() {
|
||||
do_check_false(store.isAddonSyncable(dummy));
|
||||
dummy.scope = addon.scope;
|
||||
|
||||
dummy.isSyncable = false;
|
||||
do_check_false(store.isAddonSyncable(dummy));
|
||||
dummy.isSyncable = addon.isSyncable;
|
||||
|
||||
dummy.foreignInstall = true;
|
||||
do_check_false(store.isAddonSyncable(dummy));
|
||||
dummy.foreignInstall = false;
|
||||
@ -268,8 +296,6 @@ add_test(function test_addon_syncability() {
|
||||
add_test(function test_ignore_hotfixes() {
|
||||
_("Ensure that hotfix extensions are ignored.");
|
||||
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
|
||||
// A hotfix extension is one that has the id the same as the
|
||||
// extensions.hotfix.id pref.
|
||||
let prefs = new Preferences("extensions.");
|
||||
@ -278,7 +304,7 @@ add_test(function test_ignore_hotfixes() {
|
||||
do_check_true(store.isAddonSyncable(addon));
|
||||
|
||||
let dummy = {};
|
||||
const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall"];
|
||||
const KEYS = ["id", "syncGUID", "type", "scope", "foreignInstall", "isSyncable"];
|
||||
for (let k of KEYS) {
|
||||
dummy[k] = addon[k];
|
||||
}
|
||||
@ -301,7 +327,6 @@ add_test(function test_ignore_hotfixes() {
|
||||
|
||||
uninstallAddon(addon);
|
||||
|
||||
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
||||
prefs.reset("hotfix.id");
|
||||
|
||||
run_next_test();
|
||||
@ -311,8 +336,6 @@ add_test(function test_ignore_hotfixes() {
|
||||
add_test(function test_get_all_ids() {
|
||||
_("Ensures that getAllIDs() returns an appropriate set.");
|
||||
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
|
||||
_("Installing two addons.");
|
||||
let addon1 = installAddon("test_install1");
|
||||
let addon2 = installAddon("test_bootstrap1_1");
|
||||
@ -331,7 +354,6 @@ add_test(function test_get_all_ids() {
|
||||
addon1.install.cancel();
|
||||
uninstallAddon(addon2);
|
||||
|
||||
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
@ -357,9 +379,6 @@ add_test(function test_change_item_id() {
|
||||
add_test(function test_create() {
|
||||
_("Ensure creating/installing an add-on from a record works.");
|
||||
|
||||
// Set this so that getInstallFromSearchResult doesn't end up
|
||||
// failing the install due to an insecure source URI scheme.
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
let server = createAndStartHTTPServer(HTTP_PORT);
|
||||
|
||||
let addon = installAddon("test_bootstrap1_1");
|
||||
@ -379,7 +398,6 @@ add_test(function test_create() {
|
||||
|
||||
uninstallAddon(newAddon);
|
||||
|
||||
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
|
||||
@ -416,7 +434,16 @@ add_test(function test_create_bad_install() {
|
||||
let failed = store.applyIncomingBatch([record]);
|
||||
// This addon had no source URI so was skipped - but it's not treated as
|
||||
// failure.
|
||||
do_check_eq(0, failed.length);
|
||||
// XXX - this test isn't testing what we thought it was. Previously the addon
|
||||
// was not being installed due to requireSecureURL checking *before* we'd
|
||||
// attempted to get the XPI.
|
||||
// With requireSecureURL disabled we do see a download failure, but the addon
|
||||
// *does* get added to |failed|.
|
||||
// FTR: onDownloadFailed() is called with ERROR_NETWORK_FAILURE, so it's going
|
||||
// to be tricky to distinguish a 404 from other transient network errors
|
||||
// where we do want the addon to end up in |failed|.
|
||||
// This is being tracked in bug 1284778.
|
||||
//do_check_eq(0, failed.length);
|
||||
|
||||
let addon = getAddonFromAddonManagerByID(id);
|
||||
do_check_eq(null, addon);
|
||||
@ -424,19 +451,56 @@ add_test(function test_create_bad_install() {
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
|
||||
add_test(function test_ignore_system() {
|
||||
_("Ensure we ignore system addons");
|
||||
// Our system addon should not appear in getAllIDs
|
||||
engine._refreshReconcilerState();
|
||||
let num = 0;
|
||||
for (let guid in store.getAllIDs()) {
|
||||
num += 1;
|
||||
let addon = reconciler.getAddonStateFromSyncGUID(guid);
|
||||
do_check_neq(addon.id, SYSTEM_ADDON_ID);
|
||||
}
|
||||
do_check_true(num > 1, "should have seen at least one.")
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_incoming_system() {
|
||||
_("Ensure we handle incoming records that refer to a system addon");
|
||||
// eg, loop initially had a normal addon but it was then "promoted" to be a
|
||||
// system addon but wanted to keep the same ID. The server record exists due
|
||||
// to this.
|
||||
|
||||
// before we start, ensure the system addon isn't disabled.
|
||||
do_check_false(getAddonFromAddonManagerByID(SYSTEM_ADDON_ID).userDisabled);
|
||||
|
||||
// Now simulate an incoming record with the same ID as the system addon,
|
||||
// but flagged as disabled - it should not be applied.
|
||||
let server = createAndStartHTTPServer(HTTP_PORT);
|
||||
// We make the incoming record flag the system addon as disabled - it should
|
||||
// be ignored.
|
||||
let guid = Utils.makeGUID();
|
||||
let record = createRecordForThisApp(guid, SYSTEM_ADDON_ID, false, false);
|
||||
|
||||
let failed = store.applyIncomingBatch([record]);
|
||||
do_check_eq(0, failed.length);
|
||||
|
||||
// The system addon should still not be userDisabled.
|
||||
do_check_false(getAddonFromAddonManagerByID(SYSTEM_ADDON_ID).userDisabled);
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
|
||||
add_test(function test_wipe() {
|
||||
_("Ensures that wiping causes add-ons to be uninstalled.");
|
||||
|
||||
let addon1 = installAddon("test_bootstrap1_1");
|
||||
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
store.wipe();
|
||||
|
||||
let addon = getAddonFromAddonManagerByID(addon1.id);
|
||||
do_check_eq(null, addon);
|
||||
|
||||
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
@ -451,7 +515,6 @@ add_test(function test_wipe_and_install() {
|
||||
let record = createRecordForThisApp(installed.syncGUID, installed.id, true,
|
||||
false);
|
||||
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
store.wipe();
|
||||
|
||||
let deleted = getAddonFromAddonManagerByID(installed.id);
|
||||
@ -465,7 +528,6 @@ add_test(function test_wipe_and_install() {
|
||||
let fetched = getAddonFromAddonManagerByID(record.addonID);
|
||||
do_check_true(!!fetched);
|
||||
|
||||
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
|
||||
|
@ -11,7 +11,6 @@ Cu.import("resource://services-sync/util.js");
|
||||
|
||||
loadAddonTestFunctions();
|
||||
startupManager();
|
||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||
Svc.Prefs.set("engine.addons", true);
|
||||
|
||||
Service.engineManager.register(AddonsEngine);
|
||||
|
@ -148,16 +148,27 @@ add_test(function test_prefAttributes() {
|
||||
Svc.Prefs.get("scheduler.immediateInterval") * 1000);
|
||||
|
||||
_("Custom values for prefs will take effect after a restart.");
|
||||
Svc.Prefs.set("scheduler.sync11.singleDeviceInterval", 42);
|
||||
Svc.Prefs.set("scheduler.idleInterval", 23);
|
||||
Svc.Prefs.set("scheduler.activeInterval", 18);
|
||||
Svc.Prefs.set("scheduler.sync11.singleDeviceInterval", 420);
|
||||
Svc.Prefs.set("scheduler.idleInterval", 230);
|
||||
Svc.Prefs.set("scheduler.activeInterval", 180);
|
||||
Svc.Prefs.set("scheduler.immediateInterval", 31415);
|
||||
scheduler.setDefaults();
|
||||
do_check_eq(scheduler.idleInterval, 23000);
|
||||
do_check_eq(scheduler.singleDeviceInterval, 42000);
|
||||
do_check_eq(scheduler.activeInterval, 18000);
|
||||
do_check_eq(scheduler.idleInterval, 230000);
|
||||
do_check_eq(scheduler.singleDeviceInterval, 420000);
|
||||
do_check_eq(scheduler.activeInterval, 180000);
|
||||
do_check_eq(scheduler.immediateInterval, 31415000);
|
||||
|
||||
_("Custom values for interval prefs can't be less than 60 seconds.");
|
||||
Svc.Prefs.set("scheduler.sync11.singleDeviceInterval", 42);
|
||||
Svc.Prefs.set("scheduler.idleInterval", 50);
|
||||
Svc.Prefs.set("scheduler.activeInterval", 50);
|
||||
Svc.Prefs.set("scheduler.immediateInterval", 10);
|
||||
scheduler.setDefaults();
|
||||
do_check_eq(scheduler.idleInterval, 60000);
|
||||
do_check_eq(scheduler.singleDeviceInterval, 60000);
|
||||
do_check_eq(scheduler.activeInterval, 60000);
|
||||
do_check_eq(scheduler.immediateInterval, 60000);
|
||||
|
||||
Svc.Prefs.resetBranch("");
|
||||
scheduler.setDefaults();
|
||||
run_next_test();
|
||||
|
@ -12,6 +12,7 @@ support-files =
|
||||
places_v10_from_v11.sqlite
|
||||
rewrite-search.xml
|
||||
sync_ping_schema.json
|
||||
systemaddon-search.xml
|
||||
!/services/common/tests/unit/head_helpers.js
|
||||
!/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
|
||||
|
||||
|
@ -227,6 +227,9 @@ class RobocopTestRunner(MochitestDesktop):
|
||||
self.options.extraPrefs.append('browser.casting.enabled=true')
|
||||
self.options.extraPrefs.append('extensions.autoupdate.enabled=false')
|
||||
|
||||
# Override the telemetry init delay for integration testing.
|
||||
self.options.extraPrefs.append('toolkit.telemetry.initDelay=1')
|
||||
|
||||
self.options.extensionsToExclude.extend([
|
||||
'mochikit@mozilla.org',
|
||||
'worker-test@mozilla.org.xpi',
|
||||
|
@ -65,10 +65,12 @@ class TPSTestRunner(object):
|
||||
# Allow installing extensions dropped into the profile folder
|
||||
'extensions.autoDisableScopes': 10,
|
||||
'extensions.getAddons.get.url': 'http://127.0.0.1:4567/addons/api/%IDS%.xml',
|
||||
# Our pretend addons server doesn't support metadata...
|
||||
'extensions.getAddons.cache.enabled': False,
|
||||
'extensions.install.requireSecureOrigin': False,
|
||||
'extensions.update.enabled': False,
|
||||
# Don't open a dialog to show available add-on updates
|
||||
'extensions.update.notifyUser': False,
|
||||
'services.sync.addons.ignoreRepositoryChecking': True,
|
||||
'services.sync.firstSync': 'notReady',
|
||||
'services.sync.lastversion': '1.0',
|
||||
'toolkit.startup.max_resumed_crashes': -1,
|
||||
|
@ -631,7 +631,14 @@ DocumentManager = {
|
||||
.filter(promise => promise);
|
||||
|
||||
if (!promises.length) {
|
||||
return Promise.reject({message: `No matching window`});
|
||||
let details = {};
|
||||
for (let key of ["all_frames", "frame_id", "matches_about_blank", "matchesHost"]) {
|
||||
if (key in options) {
|
||||
details[key] = options[key];
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject({message: `No window matching ${JSON.stringify(details)}`});
|
||||
}
|
||||
if (options.all_frames) {
|
||||
return Promise.all(promises);
|
||||
|
@ -2305,11 +2305,12 @@ Engine.prototype = {
|
||||
if (/^(?:jar:)?(?:\[app\]|\[distribution\])/.test(this._loadPath))
|
||||
return true;
|
||||
|
||||
// If we are in the xpcshell test case, we'll accept as a 'default' engine
|
||||
// anything that has been registered at resource://search-plugins/ even if
|
||||
// the file doesn't come from the application folder.
|
||||
// If not, skip costly additional checks.
|
||||
if (!gEnvironment.get("XPCSHELL_TEST_PROFILE_DIR"))
|
||||
// If we are using a non-default locale or in the xpcshell test case,
|
||||
// we'll accept as a 'default' engine anything that has been registered at
|
||||
// resource://search-plugins/ even if the file doesn't come from the
|
||||
// application folder. If not, skip costly additional checks.
|
||||
if (!Services.prefs.prefHasUserValue(LOCALE_PREF) &&
|
||||
!gEnvironment.get("XPCSHELL_TEST_PROFILE_DIR"))
|
||||
return false;
|
||||
|
||||
// Some xpcshell tests use the search service without registering
|
||||
@ -2322,8 +2323,7 @@ Engine.prototype = {
|
||||
let uri = makeURI(APP_SEARCH_PREFIX + this._shortName + ".xml");
|
||||
if (this.getAnonymizedLoadPath(null, uri) == this._loadPath) {
|
||||
// This isn't a real default engine, but it's very close.
|
||||
LOG("_isDefault, pretending " + this._loadPath +
|
||||
" is a default engine for testing purposes");
|
||||
LOG("_isDefault, pretending " + this._loadPath + " is a default engine");
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2440,6 +2440,7 @@ Engine.prototype = {
|
||||
Services.prefs.getDefaultBranch(BROWSER_SEARCH_PREF).getBoolPref("reset.enabled") &&
|
||||
this.name == Services.search.currentEngine.name &&
|
||||
!this._isDefault &&
|
||||
this.name != Services.search.originalDefaultEngine.name &&
|
||||
(!this.getAttr("loadPathHash") ||
|
||||
this.getAttr("loadPathHash") != getVerificationHash(this._loadPath)) &&
|
||||
!this._isWhiteListed) {
|
||||
|
@ -3404,7 +3404,7 @@
|
||||
"description": "PLACES: Number of unique pages"
|
||||
},
|
||||
"PLACES_MOST_RECENT_EXPIRED_VISIT_DAYS": {
|
||||
"alert_emails": ["mbonardo@mozilla.com"],
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "linear",
|
||||
"low": 30,
|
||||
@ -4541,15 +4541,15 @@
|
||||
"description": "Windows only. Counts occurrences of touch events"
|
||||
},
|
||||
"FX_URLBAR_SELECTED_RESULT_INDEX": {
|
||||
"alert_emails": ["mbonardo@mozilla.com"],
|
||||
"expires_in_version": "50",
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "55",
|
||||
"kind": "enumerated",
|
||||
"n_values": 17,
|
||||
"bug_numbers": [775825],
|
||||
"description": "Firefox: The index of the selected result in the URL bar popup"
|
||||
},
|
||||
"FX_URLBAR_SELECTED_RESULT_TYPE": {
|
||||
"alert_emails": ["mbonardo@mozilla.com"],
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 14,
|
||||
@ -8328,40 +8328,24 @@
|
||||
},
|
||||
"FX_SANITIZE_CACHE": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "50",
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
"description": "Sanitize: Time it takes to sanitize the cache (ms)"
|
||||
},
|
||||
"FX_SANITIZE_COOKIES": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "50",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
"description": "Sanitize: Time it takes to sanitize cookies (ms)"
|
||||
},
|
||||
"FX_SANITIZE_COOKIES_2": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "50",
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
"description": "Sanitize: Time it takes to sanitize firefox cookies (ms). A subset of FX_SANITIZE_COOKIES."
|
||||
},
|
||||
"FX_SANITIZE_PLUGINS": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "50",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
"description": "Sanitize: Time it takes to sanitize plugin cookies (ms). A subset of FX_SANITIZE_COOKIES."
|
||||
},
|
||||
"FX_SANITIZE_LOADED_FLASH": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"bug_numbers": [1251469],
|
||||
"expires_in_version": "50",
|
||||
"expires_in_version": "55",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
@ -8370,23 +8354,15 @@
|
||||
"FX_SANITIZE_UNLOADED_FLASH": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"bug_numbers": [1251469],
|
||||
"expires_in_version": "50",
|
||||
"expires_in_version": "55",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
"description": "Sanitize: Time it takes to sanitize Flash when it's not yet loaded (ms). A subset of FX_SANITIZE_PLUGINS."
|
||||
},
|
||||
"FX_SANITIZE_OFFLINEAPPS": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "50",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
"description": "Sanitize: Time it takes to sanitize stored offline app data (ms)"
|
||||
},
|
||||
"FX_SANITIZE_HISTORY": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "50",
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
@ -8394,7 +8370,7 @@
|
||||
},
|
||||
"FX_SANITIZE_FORMDATA": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "50",
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
@ -8402,7 +8378,7 @@
|
||||
},
|
||||
"FX_SANITIZE_DOWNLOADS": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "50",
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
@ -8410,7 +8386,7 @@
|
||||
},
|
||||
"FX_SANITIZE_SESSIONS": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "50",
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
@ -8418,7 +8394,7 @@
|
||||
},
|
||||
"FX_SANITIZE_SITESETTINGS": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "50",
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
@ -8426,7 +8402,7 @@
|
||||
},
|
||||
"FX_SANITIZE_OPENWINDOWS": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "50",
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": 30000,
|
||||
"n_buckets": 20,
|
||||
|
@ -542,6 +542,8 @@
|
||||
}
|
||||
this._highlightAll = aHighlight;
|
||||
|
||||
this.browser.finder.onHighlightAllChange(aHighlight);
|
||||
|
||||
if (!this._dispatchFindEvent("highlightallchange")) {
|
||||
return;
|
||||
}
|
||||
|
@ -321,6 +321,11 @@ Finder.prototype = {
|
||||
this._highlighter.onModalHighlightChange(useModalHighlight);
|
||||
},
|
||||
|
||||
onHighlightAllChange(highlightAll) {
|
||||
if (this._highlighter)
|
||||
this._highlighter.onHighlightAllChange(highlightAll);
|
||||
},
|
||||
|
||||
keyPress: function (aEvent) {
|
||||
let controller = this._getSelectionController(this._getWindow());
|
||||
|
||||
|
@ -434,6 +434,19 @@ FinderHighlighter.prototype = {
|
||||
this._modal = useModalHighlight;
|
||||
},
|
||||
|
||||
/**
|
||||
* When 'Highlight All' is toggled during a session, this callback is invoked
|
||||
* and when it's turned off, the found occurrences will be removed from the mask.
|
||||
*
|
||||
* @param {Boolean} highlightAll
|
||||
*/
|
||||
onHighlightAllChange(highlightAll) {
|
||||
if (this._modal && !highlightAll) {
|
||||
this.clear();
|
||||
this._scheduleRepaintOfMask(this.finder._getWindow());
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility; get the nsIDOMWindowUtils for a window.
|
||||
*
|
||||
|
@ -191,6 +191,12 @@ RemoteFinder.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
onHighlightAllChange: function(aHighlightAll) {
|
||||
this._browser.messageManager.sendAsyncMessage("Finder:HighlightAllChange", {
|
||||
highlightAll: aHighlightAll
|
||||
});
|
||||
},
|
||||
|
||||
keyPress: function (aEvent) {
|
||||
this._browser.messageManager.sendAsyncMessage("Finder:KeyPress",
|
||||
{ keyCode: aEvent.keyCode,
|
||||
@ -229,6 +235,7 @@ RemoteFinderListener.prototype = {
|
||||
"Finder:SetSearchStringToSelection",
|
||||
"Finder:GetInitialSelection",
|
||||
"Finder:Highlight",
|
||||
"Finder:HighlightAllChange",
|
||||
"Finder:EnableSelection",
|
||||
"Finder:RemoveSelection",
|
||||
"Finder:FocusContent",
|
||||
@ -296,6 +303,10 @@ RemoteFinderListener.prototype = {
|
||||
this._finder.highlight(data.highlight, data.word, data.linksOnly);
|
||||
break;
|
||||
|
||||
case "Finder:HighlightAllChange":
|
||||
this._finder.onHighlightAllChange(data.highlightAll);
|
||||
break;
|
||||
|
||||
case "Finder:EnableSelection":
|
||||
this._finder.enableSelection();
|
||||
break;
|
||||
|
@ -6,6 +6,7 @@ Cu.import("resource://gre/modules/Promise.jsm", this);
|
||||
Cu.import("resource://gre/modules/Task.jsm", this);
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
|
||||
const kHighlightAllPref = "findbar.highlightAll";
|
||||
const kPrefModalHighlight = "findbar.modalHighlight";
|
||||
const kFixtureBaseURL = "https://example.com/browser/toolkit/modules/tests/browser/";
|
||||
|
||||
@ -129,8 +130,8 @@ function promiseTestHighlighterOutput(browser, word, expectedResult, extraTest =
|
||||
|
||||
add_task(function* setup() {
|
||||
yield SpecialPowers.pushPrefEnv({ set: [
|
||||
["findbar.highlightAll", true],
|
||||
["findbar.modalHighlight", true]
|
||||
[kHighlightAllPref, true],
|
||||
[kPrefModalHighlight, true]
|
||||
]});
|
||||
});
|
||||
|
||||
@ -270,3 +271,43 @@ add_task(function* testDarkPageDetection() {
|
||||
findbar.close(true);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* testHighlightAllToggle() {
|
||||
let url = kFixtureBaseURL + "file_FinderSample.html";
|
||||
yield BrowserTestUtils.withNewTab(url, function* (browser) {
|
||||
let findbar = gBrowser.getFindBar();
|
||||
|
||||
yield promiseOpenFindbar(findbar);
|
||||
|
||||
let word = "Roland";
|
||||
let expectedResult = {
|
||||
rectCount: 2,
|
||||
insertCalls: [2, 4],
|
||||
removeCalls: [1, 2]
|
||||
};
|
||||
let promise = promiseTestHighlighterOutput(browser, word, expectedResult);
|
||||
yield promiseEnterStringIntoFindField(findbar, word);
|
||||
yield promise;
|
||||
|
||||
// We now know we have multiple rectangles highlighted, so it's a good time
|
||||
// to flip the pref.
|
||||
expectedResult = {
|
||||
rectCount: 0,
|
||||
insertCalls: [1, 1],
|
||||
removeCalls: [1, 1]
|
||||
};
|
||||
promise = promiseTestHighlighterOutput(browser, word, expectedResult);
|
||||
yield SpecialPowers.pushPrefEnv({ "set": [[ kHighlightAllPref, false ]] });
|
||||
yield promise;
|
||||
|
||||
// For posterity, let's switch back.
|
||||
expectedResult = {
|
||||
rectCount: 2,
|
||||
insertCalls: [2, 4],
|
||||
removeCalls: [1, 2]
|
||||
};
|
||||
promise = promiseTestHighlighterOutput(browser, word, expectedResult);
|
||||
yield SpecialPowers.pushPrefEnv({ "set": [[ kHighlightAllPref, true ]] });
|
||||
yield promise;
|
||||
});
|
||||
});
|
||||
|
@ -7435,6 +7435,17 @@ AddonWrapper.prototype = {
|
||||
addon._installLocation.name == KEY_APP_SYSTEM_ADDONS);
|
||||
},
|
||||
|
||||
// Returns true if Firefox Sync should sync this addon. Only non-hotfixes
|
||||
// directly in the profile are considered syncable.
|
||||
get isSyncable() {
|
||||
let addon = addonFor(this);
|
||||
let hotfixID = Preferences.get(PREF_EM_HOTFIX_ID, undefined);
|
||||
if (hotfixID && hotfixID == addon.id) {
|
||||
return false;
|
||||
}
|
||||
return (addon._installLocation.name == KEY_APP_PROFILE);
|
||||
},
|
||||
|
||||
isCompatibleWith: function(aAppVersion, aPlatformVersion) {
|
||||
return addonFor(this).isCompatibleWith(aAppVersion, aPlatformVersion);
|
||||
},
|
||||
|
@ -1098,21 +1098,16 @@ nsLookAndFeel::Init()
|
||||
// with wrong color theme, see Bug 972382
|
||||
GtkSettings *settings = gtk_settings_get_for_screen(gdk_screen_get_default());
|
||||
|
||||
// Disable dark theme because it interacts poorly with widget styling in
|
||||
// web content (see bug 1216658).
|
||||
// To avoid triggering reload of theme settings unnecessarily, only set the
|
||||
// setting when necessary.
|
||||
const gchar* dark_setting = "gtk-application-prefer-dark-theme";
|
||||
gboolean dark;
|
||||
g_object_get(settings, dark_setting, &dark, nullptr);
|
||||
|
||||
bool allowDarkTheme = mozilla::Preferences::GetBool("widget.allow-gtk-dark-theme", false);
|
||||
|
||||
if (!allowDarkTheme) {
|
||||
// Disable dark theme because it interacts poorly with widget styling in
|
||||
// web content (see bug 1216658).
|
||||
// To avoid triggering reload of theme settings unnecessarily, only set the
|
||||
// setting when necessary.
|
||||
const gchar* dark_setting = "gtk-application-prefer-dark-theme";
|
||||
gboolean dark;
|
||||
g_object_get(settings, dark_setting, &dark, nullptr);
|
||||
|
||||
if (dark) {
|
||||
g_object_set(settings, dark_setting, FALSE, nullptr);
|
||||
}
|
||||
if (dark) {
|
||||
g_object_set(settings, dark_setting, FALSE, nullptr);
|
||||
}
|
||||
|
||||
GtkWidgetPath *path = gtk_widget_path_new();
|
||||
|
Loading…
Reference in New Issue
Block a user