merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2016-07-29 11:55:36 +02:00
commit e7165f9b1e
91 changed files with 2166 additions and 453 deletions

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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"

View File

@ -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();
})
},

View File

@ -100,10 +100,6 @@ let gWhitelist = [{
file: "netErrorApp.dtd",
key: "securityOverride.warningContent",
type: "single-quote"
}, {
file: "sync.properties",
key: "client.name2",
type: "apostrophe"
}
];

View File

@ -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");

View File

@ -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});
},
});
}

View File

@ -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"/>

View File

@ -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,

View File

@ -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();
},
/**

View File

@ -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);
});
}

View File

@ -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() {

View File

@ -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);
});

View File

@ -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: ""});

View File

@ -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 => {

View File

@ -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"

View File

@ -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();

View File

@ -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

View File

@ -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),
});
},

View File

@ -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=' +

View File

@ -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);
}

View File

@ -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">

View File

@ -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">

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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)

View File

@ -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] {

View 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

View File

@ -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;
}

View File

@ -0,0 +1,10 @@
{
"manifest_version": 2,
"name": "test-devtools-webextension-nobg",
"version": "1.0",
"applications": {
"gecko": {
"id": "test-devtools-webextension-nobg@mozilla.org"
}
}
}

View File

@ -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");
}

View File

@ -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"
}
}

View File

@ -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>

View File

@ -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());
}

View File

@ -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]

View File

@ -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);
});

View File

@ -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);
});

View File

@ -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);
});

View File

@ -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);
});

View File

@ -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);
});

View File

@ -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);
}

View File

@ -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);
});

View File

@ -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");

View File

@ -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);
});

View File

@ -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 };
}

View File

@ -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]

View File

@ -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);
}

View File

@ -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>

View File

@ -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);
}

View File

@ -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() {

View File

@ -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 => {

View File

@ -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();
}

View File

@ -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;
});

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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();

View File

@ -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" });
}

View File

@ -62,6 +62,7 @@ DevToolsModules(
'webaudio.js',
'webbrowser.js',
'webconsole.js',
'webextension.js',
'webgl.js',
'worker.js',
)

View File

@ -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);
}
};

View File

@ -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);
}

View 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;

View File

@ -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

View File

@ -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$Ss %2$S on %3$S
# %S is the date and time at which the last sync successfully completed
lastSync2.label = Last sync: %S

View File

@ -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: " +

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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.

View File

@ -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";
};

View 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>

View File

@ -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);
});

View File

@ -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";

View File

@ -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);
});

View File

@ -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);

View File

@ -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();

View File

@ -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

View File

@ -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',

View File

@ -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,

View File

@ -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);

View File

@ -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) {

View File

@ -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,

View File

@ -542,6 +542,8 @@
}
this._highlightAll = aHighlight;
this.browser.finder.onHighlightAllChange(aHighlight);
if (!this._dispatchFindEvent("highlightallchange")) {
return;
}

View File

@ -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());

View File

@ -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.
*

View File

@ -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;

View File

@ -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;
});
});

View File

@ -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);
},

View File

@ -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();