Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-06-12 11:31:50 +02:00
Родитель 90f62c32d0 c6816bf0df
Коммит 3b5e1843a7
113 изменённых файлов: 3988 добавлений и 1394 удалений

Просмотреть файл

@ -579,6 +579,9 @@ pref("mousewheel.with_win.action", 1);
pref("browser.xul.error_pages.enabled", true); pref("browser.xul.error_pages.enabled", true);
pref("browser.xul.error_pages.expert_bad_cert", false); pref("browser.xul.error_pages.expert_bad_cert", false);
// Enable captive portal detection.
pref("network.captive-portal-service.enabled", true);
// If true, network link events will change the value of navigator.onLine // If true, network link events will change the value of navigator.onLine
pref("network.manage-offline-status", true); pref("network.manage-offline-status", true);

Просмотреть файл

@ -138,7 +138,7 @@ a {
#searchText:focus + #searchSubmit, #searchText:focus + #searchSubmit,
#searchText[keepfocus] + #searchSubmit, #searchText[keepfocus] + #searchSubmit,
#searchText[autofocus] + #searchSubmit { #searchText[autofocus] + #searchSubmit {
background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#4cb1ff, #1793e5); background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#4cb1ff, #1793e5);
box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset, box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
0 0 0 1px hsla(0,0%,100%,.1) inset, 0 0 0 1px hsla(0,0%,100%,.1) inset,
0 1px 0 hsla(210,54%,20%,.03); 0 1px 0 hsla(210,54%,20%,.03);
@ -151,7 +151,7 @@ a {
} }
#searchText + #searchSubmit:hover { #searchText + #searchSubmit:hover {
background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#66bdff, #0d9eff); background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#66bdff, #0d9eff);
box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset, box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
0 0 0 1px hsla(0,0%,100%,.1) inset, 0 0 0 1px hsla(0,0%,100%,.1) inset,
0 1px 0 hsla(210,54%,20%,.03), 0 1px 0 hsla(210,54%,20%,.03),

Просмотреть файл

@ -6895,6 +6895,10 @@ var gIdentityHandler = {
tooltip = gNavigatorBundle.getString("identity.unknown.tooltip"); tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
} }
if (SitePermissions.hasGrantedPermissions(this._uri)) {
this._identityBox.classList.add("grantedPermissions");
}
// Push the appropriate strings out to the UI // Push the appropriate strings out to the UI
this._identityBox.tooltipText = tooltip; this._identityBox.tooltipText = tooltip;
this._identityIcon.tooltipText = gNavigatorBundle.getString("identity.icon.tooltip"); this._identityIcon.tooltipText = gNavigatorBundle.getString("identity.icon.tooltip");

Просмотреть файл

@ -429,14 +429,14 @@ input[type=button] {
#newtab-search-text:focus + #newtab-search-submit, #newtab-search-text:focus + #newtab-search-submit,
#newtab-search-text[keepfocus] + #newtab-search-submit, #newtab-search-text[keepfocus] + #newtab-search-submit,
#newtab-search-text[autofocus] + #newtab-search-submit { #newtab-search-text[autofocus] + #newtab-search-submit {
background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#4cb1ff, #1793e5); background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#4cb1ff, #1793e5);
box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset, box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
0 0 0 1px hsla(0,0%,100%,.1) inset, 0 0 0 1px hsla(0,0%,100%,.1) inset,
0 1px 0 hsla(210,54%,20%,.03); 0 1px 0 hsla(210,54%,20%,.03);
} }
#newtab-search-text + #newtab-search-submit:hover { #newtab-search-text + #newtab-search-submit:hover {
background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#4cb1ff, #1793e5); background-image: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted"), linear-gradient(#4cb1ff, #1793e5);
box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset, box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
0 0 0 1px hsla(0,0%,100%,.1) inset, 0 0 0 1px hsla(0,0%,100%,.1) inset,
0 1px 0 hsla(210,54%,20%,.03), 0 1px 0 hsla(210,54%,20%,.03),

Просмотреть файл

@ -8,6 +8,10 @@ var {SitePermissions} = Cu.import("resource:///modules/SitePermissions.jsm", {})
registerCleanupFunction(function() { registerCleanupFunction(function() {
SitePermissions.remove(gBrowser.currentURI, "install"); SitePermissions.remove(gBrowser.currentURI, "install");
SitePermissions.remove(gBrowser.currentURI, "cookie");
SitePermissions.remove(gBrowser.currentURI, "geo");
SitePermissions.remove(gBrowser.currentURI, "camera");
while (gBrowser.tabs.length > 1) { while (gBrowser.tabs.length > 1) {
gBrowser.removeCurrentTab(); gBrowser.removeCurrentTab();
} }
@ -25,7 +29,7 @@ add_task(function* testMainViewVisible() {
ok(!is_hidden(emptyLabel), "List of permissions is empty"); ok(!is_hidden(emptyLabel), "List of permissions is empty");
gIdentityHandler._identityPopup.hidden = true; gIdentityHandler._identityPopup.hidden = true;
gIdentityHandler.setPermission("install", 1); gIdentityHandler.setPermission("install", SitePermissions.ALLOW);
gIdentityHandler._identityBox.click(); gIdentityHandler._identityBox.click();
ok(is_hidden(emptyLabel), "List of permissions is not empty"); ok(is_hidden(emptyLabel), "List of permissions is not empty");
@ -47,3 +51,33 @@ add_task(function* testMainViewVisible() {
ok(!is_hidden(emptyLabel), "List of permissions is empty"); ok(!is_hidden(emptyLabel), "List of permissions is empty");
gIdentityHandler._identityPopup.hidden = true; gIdentityHandler._identityPopup.hidden = true;
}); });
add_task(function* testIdentityIcon() {
let {gIdentityHandler} = gBrowser.ownerGlobal;
let tab = gBrowser.selectedTab = gBrowser.addTab();
yield promiseTabLoadEvent(tab, PERMISSIONS_PAGE);
gIdentityHandler.setPermission("geo", SitePermissions.ALLOW);
gIdentityHandler.refreshIdentityBlock();
ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
"identity-box signals granted permssions");
gIdentityHandler.setPermission("geo", SitePermissions.getDefault("geo"));
gIdentityHandler.refreshIdentityBlock();
ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
"identity-box doesn't signal granted permssions");
gIdentityHandler.setPermission("camera", SitePermissions.BLOCK);
gIdentityHandler.refreshIdentityBlock();
ok(!gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
"identity-box doesn't signal granted permssions");
gIdentityHandler.setPermission("cookie", SitePermissions.SESSION);
gIdentityHandler.refreshIdentityBlock();
ok(gIdentityHandler._identityBox.classList.contains("grantedPermissions"),
"identity-box signals granted permssions");
});

Просмотреть файл

@ -235,17 +235,7 @@ this.DownloadsViewUI.DownloadElementShell.prototype = {
} else if (this.download.error.becauseBlockedByParentalControls) { } else if (this.download.error.becauseBlockedByParentalControls) {
stateLabel = s.stateBlockedParentalControls; stateLabel = s.stateBlockedParentalControls;
} else if (this.download.error.becauseBlockedByReputationCheck) { } else if (this.download.error.becauseBlockedByReputationCheck) {
switch (this.download.error.reputationCheckVerdict) { stateLabel = this.rawBlockedTitleAndDetails[0];
case Downloads.Error.BLOCK_VERDICT_UNCOMMON:
stateLabel = s.blockedUncommon2;
break;
case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
stateLabel = s.blockedPotentiallyUnwanted;
break;
default: // Assume Downloads.Error.BLOCK_VERDICT_MALWARE
stateLabel = s.blockedMalware;
break;
}
} else { } else {
stateLabel = s.stateFailed; stateLabel = s.stateFailed;
} }
@ -264,6 +254,30 @@ this.DownloadsViewUI.DownloadElementShell.prototype = {
return { text, tip: tip || text }; return { text, tip: tip || text };
}, },
/**
* Returns [title, [details1, details2]] for blocked downloads.
*/
get rawBlockedTitleAndDetails() {
let s = DownloadsCommon.strings;
if (!this.download.error ||
!this.download.error.becauseBlockedByReputationCheck) {
return [null, null];
}
switch (this.download.error.reputationCheckVerdict) {
case Downloads.Error.BLOCK_VERDICT_UNCOMMON:
return [s.blockedUncommon2, [s.unblockTypeUncommon2, s.unblockTip2]];
case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
return [s.blockedPotentiallyUnwanted,
[s.unblockTypePotentiallyUnwanted2, s.unblockTip2]];
case Downloads.Error.BLOCK_VERDICT_MALWARE:
return [s.blockedMalware, [s.unblockTypeMalware, s.unblockTip2]];
}
throw new Error("Unexpected reputationCheckVerdict: " +
this.download.error.reputationCheckVerdict);
// return anyway to avoid a JS strict warning.
return [null, null];
},
/** /**
* Shows the appropriate unblock dialog based on the verdict, and executes the * Shows the appropriate unblock dialog based on the verdict, and executes the
* action selected by the user in the dialog, which may involve unblocking, * action selected by the user in the dialog, which may involve unblocking,
@ -308,8 +322,9 @@ this.DownloadsViewUI.DownloadElementShell.prototype = {
case Ci.nsIDownloadManager.DOWNLOAD_FINISHED: case Ci.nsIDownloadManager.DOWNLOAD_FINISHED:
return "downloadsCmd_open"; return "downloadsCmd_open";
case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL: case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL:
case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
return "downloadsCmd_openReferrer"; return "downloadsCmd_openReferrer";
case Ci.nsIDownloadManager.DOWNLOAD_DIRTY:
return "downloadsCmd_showBlockedInfo";
} }
return ""; return "";
}, },

Просмотреть файл

@ -71,6 +71,9 @@
<xul:button class="downloadButton downloadChooseOpen downloadIconShow" <xul:button class="downloadButton downloadChooseOpen downloadIconShow"
tooltiptext="&cmd.chooseOpen.label;" tooltiptext="&cmd.chooseOpen.label;"
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_chooseOpen');"/> oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_chooseOpen');"/>
<xul:button class="downloadButton downloadShowBlockedInfo"
tooltiptext="&cmd.chooseUnblock.label;"
oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_showBlockedInfo');"/>
</xul:stack> </xul:stack>
</content> </content>
</binding> </binding>

Просмотреть файл

@ -143,8 +143,89 @@ richlistitem.download button {
.downloadRetry, .downloadRetry,
.download-state:not( [state="1"] /* Finished */) .download-state:not( [state="1"] /* Finished */)
.downloadShow .downloadShow,
/* The "show blocked info" button is shown only in the downloads panel. */
.downloadShowBlockedInfo
{ {
visibility: hidden; display: none;
}
/*** Downloads panel ***/
#downloadsPanel[hasdownloads] #emptyDownloads,
#downloadsPanel:not([hasdownloads]) #downloadsListBox {
display: none;
}
/*** 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,
#downloadsPanel-mainView .download-state[state="8"] .downloadChooseUnblock,
#downloadsPanel-mainView .download-state[state="8"] .downloadChooseOpen,
#downloadsPanel-mainView .download-state[state="8"] .downloadRetry,
#downloadsPanel-mainView .download-state[state="8"] .downloadShow {
display: none;
}
/* Show the "show blocked info" button. */
#downloadsPanel-mainView .download-state[state="8"] .downloadShowBlockedInfo {
display: inline;
}
/** When the main view is showing... **/
/* The subview should be off to the right and not visible at all. */
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews {
transform: translateX(100%);
transition: transform var(--panelui-subview-transition-duration);
}
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews:-moz-locale-dir(rtl) {
transform: translateX(-100%);
}
/** When the subview is showing... **/
/* Hide the buttons of all downloads except the one that triggered the
subview. */
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state:not([showingsubview]) .downloadButton {
display: none;
}
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadButton {
display: inline;
}
/* For the download that triggered the subview, move its button farther to the
right by removing padding so that a minimum amount of the main view's right
edge needs to be shown. */
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] {
padding: 0;
}
/* The main view should slide to the left and its right edge should remain
visible. */
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=subview] > .panel-mainview {
transform: translateX(calc(-100% + 38px));
transition: transform var(--panelui-subview-transition-duration);
}
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=subview] > .panel-mainview:-moz-locale-dir(rtl) {
transform: translateX(calc(100% - 38px));
}
/* The subview should leave the right edge of the main view uncovered. */
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
/* Use a margin instead of a transform like above so that the subview's width
isn't wider than the panel. */
-moz-margin-start: 38px !important;
} }

Просмотреть файл

@ -912,7 +912,16 @@ const DownloadsView = {
// Handle primary clicks only, and exclude the action button. // Handle primary clicks only, and exclude the action button.
if (aEvent.button == 0 && if (aEvent.button == 0 &&
!aEvent.originalTarget.hasAttribute("oncommand")) { !aEvent.originalTarget.hasAttribute("oncommand")) {
goDoCommand("downloadsCmd_open"); let target = aEvent.target;
while (target.nodeName != "richlistitem") {
target = target.parentNode;
}
let download = DownloadsView.itemForElement(target).download;
if (download.hasBlockedData) {
goDoCommand("downloadsCmd_showBlockedInfo");
} else {
goDoCommand("downloadsCmd_open");
}
} }
}, },
@ -1070,6 +1079,8 @@ DownloadsViewItem.prototype = {
case "downloadsCmd_copyLocation": case "downloadsCmd_copyLocation":
case "downloadsCmd_doDefault": case "downloadsCmd_doDefault":
return true; return true;
case "downloadsCmd_showBlockedInfo":
return this.download.hasBlockedData;
} }
return DownloadsViewUI.DownloadElementShell.prototype return DownloadsViewUI.DownloadElementShell.prototype
.isCommandEnabled.call(this, aCommand); .isCommandEnabled.call(this, aCommand);
@ -1128,6 +1139,11 @@ DownloadsViewItem.prototype = {
DownloadsPanel.hidePanel(); DownloadsPanel.hidePanel();
}, },
downloadsCmd_showBlockedInfo() {
DownloadsBlockedSubview.show(this.element,
...this.rawBlockedTitleAndDetails);
},
downloadsCmd_openReferrer() { downloadsCmd_openReferrer() {
openURL(this.download.source.referrer); openURL(this.download.source.referrer);
}, },
@ -1481,3 +1497,123 @@ const DownloadsFooter = {
}; };
XPCOMUtils.defineConstant(this, "DownloadsFooter", DownloadsFooter); XPCOMUtils.defineConstant(this, "DownloadsFooter", DownloadsFooter);
////////////////////////////////////////////////////////////////////////////////
//// DownloadsBlockedSubview
/**
* Manages the blocked subview that slides in when you click a blocked download.
*/
const DownloadsBlockedSubview = {
get subview() {
let subview = document.getElementById("downloadsPanel-blockedSubview");
delete this.subview;
return this.subview = subview;
},
/**
* Elements in the subview.
*/
get elements() {
let idSuffixes = [
"title",
"details1",
"details2",
"openButton",
"deleteButton",
];
let elements = idSuffixes.reduce((memo, s) => {
memo[s] = document.getElementById("downloadsPanel-blockedSubview-" + s);
return memo;
}, {});
delete this.elements;
return this.elements = elements;
},
/**
* The multiview that contains both the main view and the subview.
*/
get view() {
let view = document.getElementById("downloadsPanel-multiView");
delete this.view;
return this.view = view;
},
/**
* The blocked-download richlistitem element that was clicked to show the
* subview. If the subview is not showing, this is undefined.
*/
element: undefined,
/**
* Slides in the blocked subview.
*
* @param element
* The blocked-download richlistitem element that was clicked.
* @param title
* The title to show in the subview.
* @param details
* An array of strings with information about the block.
*/
show(element, title, details) {
if (this.view.showingSubView) {
return;
}
this.element = element;
element.setAttribute("showingsubview", "true");
let e = this.elements;
let s = DownloadsCommon.strings;
e.title.textContent = title;
e.details1.textContent = details[0];
e.details2.textContent = details[1];
e.openButton.label = s.unblockButtonOpen;
e.deleteButton.label = s.unblockButtonConfirmBlock;
let verdict = element.getAttribute("verdict");
this.subview.setAttribute("verdict", verdict);
this.subview.addEventListener("ViewHiding", this);
this.view.showSubView(this.subview.id);
// Without this, the mainView is more narrow than the panel once all
// downloads are removed from the panel.
document.getElementById("downloadsPanel-mainView").style.minWidth =
window.getComputedStyle(this.view).width;
},
handleEvent(event) {
switch (event.type) {
case "ViewHiding":
this.subview.removeEventListener(event.type, this);
this.element.removeAttribute("showingsubview");
delete this.element;
break;
default:
DownloadsCommon.log("Unhandled DownloadsBlockedSubview event: " +
event.type);
break;
}
},
/**
* Slides out the blocked subview and shows the main view.
*/
hide() {
this.view.showMainView();
},
/**
* Deletes the download and hides the entire panel.
*/
confirmBlock() {
goDoCommand("cmd_delete");
DownloadsPanel.hidePanel();
},
};
XPCOMUtils.defineConstant(this, "DownloadsBlockedSubview",
DownloadsBlockedSubview);

Просмотреть файл

@ -108,46 +108,76 @@
accesskey="&cmd.clearList.accesskey;"/> accesskey="&cmd.clearList.accesskey;"/>
</menupopup> </menupopup>
<richlistbox id="downloadsListBox" <panelmultiview id="downloadsPanel-multiView"
class="plain" mainViewId="downloadsPanel-mainView"
align="stretch">
<panelview id="downloadsPanel-mainView"
flex="1" flex="1"
context="downloadsContextMenu" align="stretch">
onmouseover="DownloadsView.onDownloadMouseOver(event);" <richlistbox id="downloadsListBox"
onmouseout="DownloadsView.onDownloadMouseOut(event);" class="plain"
oncontextmenu="DownloadsView.onDownloadContextMenu(event);" context="downloadsContextMenu"
ondragstart="DownloadsView.onDownloadDragStart(event);"/> onmouseover="DownloadsView.onDownloadMouseOver(event);"
<description id="emptyDownloads" onmouseout="DownloadsView.onDownloadMouseOut(event);"
mousethrough="always"> oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
&downloadsPanelEmpty.label; ondragstart="DownloadsView.onDownloadDragStart(event);"/>
</description> <description id="emptyDownloads"
mousethrough="always">
<vbox id="downloadsFooter"> &downloadsPanelEmpty.label;
<hbox id="downloadsSummary" </description>
align="center" <spacer flex="1"/>
orient="horizontal" <vbox id="downloadsFooter">
onkeydown="DownloadsSummary.onKeyDown(event);" <hbox id="downloadsSummary"
onclick="DownloadsSummary.onClick(event);"> align="center"
<image class="downloadTypeIcon" /> orient="horizontal"
<vbox> onkeydown="DownloadsSummary.onKeyDown(event);"
<description id="downloadsSummaryDescription" onclick="DownloadsSummary.onClick(event);">
style="min-width: &downloadsSummary.minWidth2;"/> <image class="downloadTypeIcon" />
<progressmeter id="downloadsSummaryProgress" <vbox>
class="downloadProgress" <description id="downloadsSummaryDescription"
min="0" style="min-width: &downloadsSummary.minWidth2;"/>
max="100" <progressmeter id="downloadsSummaryProgress"
mode="normal" /> class="downloadProgress"
<description id="downloadsSummaryDetails" min="0"
style="width: &downloadDetails.width;" max="100"
crop="end"/> mode="normal" />
<description id="downloadsSummaryDetails"
style="width: &downloadDetails.width;"
crop="end"/>
</vbox>
</hbox>
<button id="downloadsHistory"
class="plain"
label="&downloadsHistory.label;"
accesskey="&downloadsHistory.accesskey;"
oncommand="DownloadsPanel.showDownloadsHistory();"/>
</vbox> </vbox>
</hbox> </panelview>
<panelview id="downloadsPanel-blockedSubview"
orient="vertical"
flex="1">
<description id="downloadsPanel-blockedSubview-title"/>
<description id="downloadsPanel-blockedSubview-details1"/>
<description id="downloadsPanel-blockedSubview-details2"/>
<spacer flex="1"/>
<hbox id="downloadsPanel-blockedSubview-buttons"
align="stretch">
<button id="downloadsPanel-blockedSubview-openButton"
class="plain"
command="downloadsCmd_chooseOpen"
flex="1"/>
<button id="downloadsPanel-blockedSubview-deleteButton"
class="plain"
oncommand="DownloadsBlockedSubview.confirmBlock();"
default="true"
flex="1"/>
</hbox>
</panelview>
</panelmultiview>
<button id="downloadsHistory"
class="plain"
label="&downloadsHistory.label;"
accesskey="&downloadsHistory.accesskey;"
oncommand="DownloadsPanel.showDownloadsHistory();"/>
</vbox>
</panel> </panel>
</popupset> </popupset>
</overlay> </overlay>

Просмотреть файл

@ -9,3 +9,4 @@ skip-if = os == "linux" # Bug 949434
skip-if = os == "linux" # Bug 952422 skip-if = os == "linux" # Bug 952422
[browser_confirm_unblock_download.js] [browser_confirm_unblock_download.js]
[browser_iframe_gone_mid_download.js] [browser_iframe_gone_mid_download.js]
[browser_downloads_panel_block.js]

Просмотреть файл

@ -7,32 +7,8 @@
registerCleanupFunction(() => {}); registerCleanupFunction(() => {});
function addDialogOpenObserver(buttonAction) {
Services.ww.registerNotification(function onOpen(subj, topic, data) {
if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
// The test listens for the "load" event which guarantees that the alert
// class has already been added (it is added when "DOMContentLoaded" is
// fired).
subj.addEventListener("load", function onLoad() {
subj.removeEventListener("load", onLoad);
if (subj.document.documentURI ==
"chrome://global/content/commonDialog.xul") {
Services.ww.unregisterNotification(onOpen);
let dialog = subj.document.getElementById("commonDialog");
ok(dialog.classList.contains("alert-dialog"),
"The dialog element should contain an alert class.");
let doc = subj.document.documentElement;
doc.getButton(buttonAction).click();
}
});
}
});
}
function* assertDialogResult({ args, buttonToClick, expectedResult }) { function* assertDialogResult({ args, buttonToClick, expectedResult }) {
addDialogOpenObserver(buttonToClick); promiseAlertDialogOpen(buttonToClick);
is(yield DownloadsCommon.confirmUnblockDownload(args), expectedResult); is(yield DownloadsCommon.confirmUnblockDownload(args), expectedResult);
} }

Просмотреть файл

@ -0,0 +1,165 @@
"use strict";
add_task(function* mainTest() {
yield task_resetState();
let verdicts = [
Downloads.Error.BLOCK_VERDICT_UNCOMMON,
Downloads.Error.BLOCK_VERDICT_MALWARE,
Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED,
];
yield task_addDownloads(verdicts.map(v => makeDownload(v)));
// Check that the richlistitem for each download is correct.
for (let i = 0; i < verdicts.length; i++) {
yield openPanel();
// The current item is always the first one in the listbox since each
// iteration of this loop removes the item at the end.
let item = DownloadsView.richListBox.firstChild;
// Open the panel and click the item to show the subview.
EventUtils.sendMouseEvent({ type: "click" }, item);
yield promiseSubviewShown(true);
// Items are listed in newest-to-oldest order, so e.g. the first item's
// verdict is the last element in the verdicts array.
Assert.ok(DownloadsBlockedSubview.subview.getAttribute("verdict"),
verdicts[verdicts.count - i - 1]);
// Click the sliver of the main view that's still showing on the left to go
// back to it.
EventUtils.synthesizeMouse(DownloadsPanel.panel, 10, 10, {}, window);
yield promiseSubviewShown(false);
// Show the subview again.
EventUtils.sendMouseEvent({ type: "click" }, item);
yield promiseSubviewShown(true);
// Click the Open button. The alert blocked-download dialog should be
// shown.
let dialogPromise = promiseAlertDialogOpen("cancel");
DownloadsBlockedSubview.elements.openButton.click();
yield dialogPromise;
window.focus();
yield SimpleTest.promiseFocus(window);
// Reopen the panel and show the subview again.
yield openPanel();
EventUtils.sendMouseEvent({ type: "click" }, item);
yield promiseSubviewShown(true);
// Click the Remove button. The panel should close and the item should be
// removed from it.
DownloadsBlockedSubview.elements.deleteButton.click();
yield promisePanelHidden();
yield openPanel();
Assert.ok(!item.parentNode);
DownloadsPanel.hidePanel();
yield promisePanelHidden();
}
yield task_resetState();
});
function* openPanel() {
// This function is insane but something intermittently causes the panel to be
// closed as soon as it's opening on Linux ASAN. Maybe it would also happen
// on other build machines if the test ran often enough. Not only is the
// panel closed, it's closed while it's opening, leaving DownloadsPanel._state
// such that when you try to open the panel again, it thinks it's already
// open, but it's not. The result is that the test times out.
//
// What this does is call DownloadsPanel.showPanel over and over again until
// the panel is really open. There are a few wrinkles:
//
// (1) When panel.state is "open", check four more times (for a total of five)
// before returning to make the panel stays open.
// (2) If the panel is not open, check the _state. It should be either
// kStateUninitialized or kStateHidden. If it's not, then the panel is in the
// process of opening -- or maybe it's stuck in that process -- so reset the
// _state to kStateHidden.
// (3) If the _state is not kStateUninitialized or kStateHidden, then it may
// actually be properly opening and not stuck at all. To avoid always closing
// the panel while it's properly opening, use an exponential backoff mechanism
// for retries.
//
// If all that fails, then the test will time out, but it would have timed out
// anyway.
yield promiseFocus();
yield new Promise(resolve => {
let verifyCount = 5;
let backoff = 0;
let iBackoff = 0;
let interval = setInterval(() => {
if (DownloadsPanel.panel && DownloadsPanel.panel.state == "open") {
if (verifyCount > 0) {
verifyCount--;
} else {
clearInterval(interval);
resolve();
}
} else {
if (iBackoff < backoff) {
// Keep backing off before trying again.
iBackoff++;
} else {
// Try (or retry) opening the panel.
verifyCount = 5;
backoff = Math.max(1, 2 * backoff);
iBackoff = 0;
if (DownloadsPanel._state != DownloadsPanel.kStateUninitialized) {
DownloadsPanel._state = DownloadsPanel.kStateHidden;
}
DownloadsPanel.showPanel();
}
}
}, 100);
});
}
function promisePanelHidden() {
return new Promise(resolve => {
if (!DownloadsPanel.panel || DownloadsPanel.panel.state == "closed") {
resolve();
return;
}
DownloadsPanel.panel.addEventListener("popuphidden", function onHidden() {
DownloadsPanel.panel.removeEventListener("popuphidden", onHidden);
setTimeout(resolve, 0);
});
});
}
function makeDownload(verdict) {
return {
state: nsIDM.DOWNLOAD_DIRTY,
hasBlockedData: true,
errorObj: {
result: Components.results.NS_ERROR_FAILURE,
message: "Download blocked.",
becauseBlocked: true,
becauseBlockedByReputationCheck: true,
reputationCheckVerdict: verdict,
},
};
}
function promiseSubviewShown(shown) {
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();
});
});
}

Просмотреть файл

@ -145,22 +145,32 @@ function task_resetState()
yield promiseFocus(); yield promiseFocus();
} }
function task_addDownloads(aItems) function* task_addDownloads(aItems)
{ {
let startTimeMs = Date.now(); let startTimeMs = Date.now();
let publicList = yield Downloads.getList(Downloads.PUBLIC); let publicList = yield Downloads.getList(Downloads.PUBLIC);
for (let item of aItems) { for (let item of aItems) {
publicList.add(yield Downloads.createDownload({ let download = {
source: "http://www.example.com/test-download.txt", source: {
target: gTestTargetFile, url: "http://www.example.com/test-download.txt",
},
target: {
path: gTestTargetFile.path,
},
succeeded: item.state == nsIDM.DOWNLOAD_FINISHED, succeeded: item.state == nsIDM.DOWNLOAD_FINISHED,
canceled: item.state == nsIDM.DOWNLOAD_CANCELED || canceled: item.state == nsIDM.DOWNLOAD_CANCELED ||
item.state == nsIDM.DOWNLOAD_PAUSED, item.state == nsIDM.DOWNLOAD_PAUSED,
error: item.state == nsIDM.DOWNLOAD_FAILED ? new Error("Failed.") : null, error: item.state == nsIDM.DOWNLOAD_FAILED ? new Error("Failed.") : null,
hasPartialData: item.state == nsIDM.DOWNLOAD_PAUSED, hasPartialData: item.state == nsIDM.DOWNLOAD_PAUSED,
hasBlockedData: item.hasBlockedData || false,
startTime: new Date(startTimeMs++), startTime: new Date(startTimeMs++),
})); };
// `"errorObj" in download` must be false when there's no error.
if (item.errorObj) {
download.errorObj = item.errorObj;
}
yield publicList.add(yield Downloads.createDownload(download));
} }
} }
@ -172,3 +182,30 @@ function task_openPanel()
DownloadsPanel.showPanel(); DownloadsPanel.showPanel();
yield promise; yield promise;
} }
function promiseAlertDialogOpen(buttonAction) {
return new Promise(resolve => {
Services.ww.registerNotification(function onOpen(subj, topic, data) {
if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
// The test listens for the "load" event which guarantees that the alert
// class has already been added (it is added when "DOMContentLoaded" is
// fired).
subj.addEventListener("load", function onLoad() {
subj.removeEventListener("load", onLoad);
if (subj.document.documentURI ==
"chrome://global/content/commonDialog.xul") {
Services.ww.unregisterNotification(onOpen);
let dialog = subj.document.getElementById("commonDialog");
ok(dialog.classList.contains("alert-dialog"),
"The dialog element should contain an alert class.");
let doc = subj.document.documentElement;
doc.getButton(buttonAction).click();
resolve();
}
});
}
});
});
}

Просмотреть файл

@ -21,6 +21,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
XPCOMUtils.defineLazyModuleGetter(this, "AboutNewTab", XPCOMUtils.defineLazyModuleGetter(this, "AboutNewTab",
"resource:///modules/AboutNewTab.jsm"); "resource:///modules/AboutNewTab.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "CaptivePortalWatcher",
"resource:///modules/CaptivePortalWatcher.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DirectoryLinksProvider", XPCOMUtils.defineLazyModuleGetter(this, "DirectoryLinksProvider",
"resource:///modules/DirectoryLinksProvider.jsm"); "resource:///modules/DirectoryLinksProvider.jsm");
@ -1150,6 +1153,8 @@ BrowserGlue.prototype = {
this.checkForPendingCrashReports(); this.checkForPendingCrashReports();
} }
CaptivePortalWatcher.init();
this._firstWindowTelemetry(aWindow); this._firstWindowTelemetry(aWindow);
this._firstWindowLoaded(); this._firstWindowLoaded();
}, },
@ -1175,6 +1180,8 @@ BrowserGlue.prototype = {
SelfSupportBackend.uninit(); SelfSupportBackend.uninit();
NewTabMessages.uninit(); NewTabMessages.uninit();
CaptivePortalWatcher.uninit();
AboutNewTab.uninit(); AboutNewTab.uninit();
webrtcUI.uninit(); webrtcUI.uninit();
FormValidationHandler.uninit(); FormValidationHandler.uninit();

Просмотреть файл

@ -10,10 +10,13 @@ describe("loop.conversation", function () {
var TestUtils = React.addons.TestUtils; var TestUtils = React.addons.TestUtils;
var sharedActions = loop.shared.actions; var sharedActions = loop.shared.actions;
var fakeWindow, sandbox, setLoopPrefStub, mozL10nGet, var fakeWindow, sandbox, setLoopPrefStub, mozL10nGet,
remoteCursorStore, dispatcher, requestStubs; remoteCursorStore, dispatcher, requestStubs, clock;
beforeEach(function () { beforeEach(function () {
sandbox = LoopMochaUtils.createSandbox(); sandbox = LoopMochaUtils.createSandbox();
// This ensures that the timers in ConversationToolbar are stubbed, as the
// get called when the AppControllerView is mounted.
clock = sandbox.useFakeTimers();
setLoopPrefStub = sandbox.stub(); setLoopPrefStub = sandbox.stub();
LoopMochaUtils.stubLoopRequest(requestStubs = { LoopMochaUtils.stubLoopRequest(requestStubs = {
@ -98,6 +101,7 @@ describe("loop.conversation", function () {
afterEach(function () { afterEach(function () {
loop.shared.mixins.setRootObject(window); loop.shared.mixins.setRootObject(window);
clock.restore();
sandbox.restore(); sandbox.restore();
LoopMochaUtils.restore();}); LoopMochaUtils.restore();});

Просмотреть файл

@ -0,0 +1,180 @@
/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
/**
* This constant is chosen to be large enough for a portal recheck to complete,
* and small enough that the delay in opening a tab isn't too noticeable.
* Please see comments for _delayedAddCaptivePortalTab for more details.
*/
const PORTAL_RECHECK_DELAY_MS = 150;
this.EXPORTED_SYMBOLS = [ "CaptivePortalWatcher" ];
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Timer.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "cps",
"@mozilla.org/network/captive-portal-service;1",
"nsICaptivePortalService");
this.CaptivePortalWatcher = {
// This holds a weak reference to the captive portal tab so that we
// don't leak it if the user closes it.
_captivePortalTab: null,
_initialized: false,
/**
* If a portal is detected when we don't have focus, we first wait for focus
* and then add the tab after a small delay. This is set to true while we wait
* so that in the unlikely event that we receive another notification while
* waiting, we can avoid adding a second tab.
*/
_waitingToAddTab: false,
get canonicalURL() {
return Services.prefs.getCharPref("captivedetect.canonicalURL");
},
init() {
Services.obs.addObserver(this, "captive-portal-login", false);
Services.obs.addObserver(this, "captive-portal-login-abort", false);
Services.obs.addObserver(this, "captive-portal-login-success", false);
this._initialized = true;
if (cps.state == cps.LOCKED_PORTAL) {
// A captive portal has already been detected.
this._addCaptivePortalTab();
}
},
uninit() {
if (!this._initialized) {
return;
}
Services.obs.removeObserver(this, "captive-portal-login");
Services.obs.removeObserver(this, "captive-portal-login-abort");
Services.obs.removeObserver(this, "captive-portal-login-success");
},
observe(subject, topic, data) {
switch(topic) {
case "captive-portal-login":
this._addCaptivePortalTab();
break;
case "captive-portal-login-abort":
case "captive-portal-login-success":
this._captivePortalGone();
break;
case "xul-window-visible":
this._delayedAddCaptivePortalTab();
break;
}
},
_addCaptivePortalTab() {
if (this._waitingToAddTab) {
return;
}
let win = RecentWindow.getMostRecentBrowserWindow();
// If there's no browser window or none have focus, open and show the
// tab when we regain focus. This is so that if a different application was
// focused, when the user (re-)focuses a browser window, we open the tab
// immediately in that window so they can login before continuing to browse.
if (!win || !win.document.hasFocus()) {
this._waitingToAddTab = true;
Services.obs.addObserver(this, "xul-window-visible", false);
return;
}
// The browser is in use - add the tab without selecting it.
let tab = win.gBrowser.addTab(this.canonicalURL);
this._captivePortalTab = Cu.getWeakReference(tab);
return;
},
/**
* Called after we regain focus if we detect a portal while a browser window
* doesn't have focus. Triggers a portal recheck to reaffirm state, and adds
* the tab if needed after a short delay to allow the recheck to complete.
*/
_delayedAddCaptivePortalTab() {
if (!this._waitingToAddTab) {
return;
}
let win = RecentWindow.getMostRecentBrowserWindow();
if (!win.document.hasFocus()) {
// The document that got focused was not in a browser window.
return;
}
Services.obs.removeObserver(this, "xul-window-visible");
// Trigger a portal recheck. The user may have logged into the portal via
// another client, or changed networks.
let lastChecked = cps.lastChecked;
cps.recheckCaptivePortal();
// We wait for PORTAL_RECHECK_DELAY_MS after the trigger.
// - If the portal is no longer locked, we don't need to add a tab.
// - If it is, the delay is chosen to not be extremely noticeable.
setTimeout(() => {
this._waitingToAddTab = false;
if (cps.state != cps.LOCKED_PORTAL) {
// We're free of the portal!
return;
}
let tab = win.gBrowser.addTab(this.canonicalURL);
// Focus the tab only if the recheck has completed, i.e. we're sure
// that the portal is still locked. This way, if the recheck completes
// after we add the tab and we're free of the portal, the tab contents
// won't flicker.
if (cps.lastChecked != lastChecked) {
win.gBrowser.selectedTab = tab;
}
this._captivePortalTab = Cu.getWeakReference(tab);
}, PORTAL_RECHECK_DELAY_MS);
},
_captivePortalGone() {
if (this._waitingToAddTab) {
Services.obs.removeObserver(this, "xul-window-visible");
this._waitingToAddTab = false;
}
if (!this._captivePortalTab) {
return;
}
let tab = this._captivePortalTab.get();
// In all the cases below, we want to stop treating the tab as a
// captive portal tab.
this._captivePortalTab = null;
// Check parentNode in case the object hasn't been gc'd yet.
if (!tab || tab.closing || !tab.parentNode) {
// User has closed the tab already.
return;
}
let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
// If after the login, the captive portal has redirected to some other page,
// leave it open if the tab has focus.
if (tab.linkedBrowser.currentURI.spec != this.canonicalURL &&
tabbrowser.selectedTab == tab) {
return;
}
// Remove the tab.
tabbrowser.removeTab(tab);
},
};

Просмотреть файл

@ -14,6 +14,7 @@ EXTRA_JS_MODULES += [
'AboutHome.jsm', 'AboutHome.jsm',
'AboutNewTab.jsm', 'AboutNewTab.jsm',
'BrowserUITelemetry.jsm', 'BrowserUITelemetry.jsm',
'CaptivePortalWatcher.jsm',
'CastingApps.jsm', 'CastingApps.jsm',
'Chat.jsm', 'Chat.jsm',
'ContentClick.jsm', 'ContentClick.jsm',

Просмотреть файл

@ -6,6 +6,8 @@ support-files =
[browser_BrowserUITelemetry_defaults.js] [browser_BrowserUITelemetry_defaults.js]
[browser_BrowserUITelemetry_sidebar.js] [browser_BrowserUITelemetry_sidebar.js]
[browser_BrowserUITelemetry_syncedtabs.js] [browser_BrowserUITelemetry_syncedtabs.js]
[browser_CaptivePortalWatcher.js]
skip-if = os == "linux" || os == "win" # Bug 1279491
[browser_ProcessHangNotifications.js] [browser_ProcessHangNotifications.js]
skip-if = !e10s skip-if = !e10s
[browser_ContentSearch.js] [browser_ContentSearch.js]

Просмотреть файл

@ -0,0 +1,191 @@
"use strict";
Components.utils.import("resource:///modules/RecentWindow.jsm");
const CANONICAL_CONTENT = "success";
const CANONICAL_URL = "data:text/plain;charset=utf-8," + CANONICAL_CONTENT;
const CANONICAL_URL_REDIRECTED = "data:text/plain;charset=utf-8,redirected";
add_task(function* setup() {
yield SpecialPowers.pushPrefEnv({
set: [["captivedetect.canonicalURL", CANONICAL_URL],
["captivedetect.canonicalContent", CANONICAL_CONTENT]],
});
});
/**
* We can't close the original window opened by mochitest without failing, so
* override RecentWindow.getMostRecentBrowserWindow to make CaptivePortalWatcher
* think there's no window.
*/
function* portalDetectedNoBrowserWindow() {
let getMostRecentBrowserWindow = RecentWindow.getMostRecentBrowserWindow;
RecentWindow.getMostRecentBrowserWindow = () => {};
Services.obs.notifyObservers(null, "captive-portal-login", null);
RecentWindow.getMostRecentBrowserWindow = getMostRecentBrowserWindow;
}
function* openWindowAndWaitForPortalTab() {
let win = yield BrowserTestUtils.openNewBrowserWindow();
let tab = yield BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
is(win.gBrowser.selectedTab, tab,
"The captive portal tab should be open and selected in the new window.");
return win;
}
function freePortal(aSuccess) {
Services.obs.notifyObservers(null,
"captive-portal-login-" + (aSuccess ? "success" : "abort"), null);
}
// Each of the test cases below is run twice: once for login-success and once
// for login-abort (aSuccess set to true and false respectively).
let testCasesForBothSuccessAndAbort = [
/**
* A portal is detected when there's no browser window,
* then a browser window is opened, then the portal is freed.
* The portal tab should be added and focused when the window is
* opened, and closed automatically when the success event is fired.
*/
function* test_detectedWithNoBrowserWindow_Open(aSuccess) {
yield portalDetectedNoBrowserWindow();
let win = yield openWindowAndWaitForPortalTab();
freePortal(aSuccess);
is(win.gBrowser.tabs.length, 1,
"The captive portal tab should have been closed.");
yield BrowserTestUtils.closeWindow(win);
},
/**
* A portal is detected when there's no browser window, and the
* portal is freed before a browser window is opened. No portal
* tab should be added when a browser window is opened.
*/
function* test_detectedWithNoBrowserWindow_GoneBeforeOpen(aSuccess) {
yield portalDetectedNoBrowserWindow();
freePortal(aSuccess);
let win = yield BrowserTestUtils.openNewBrowserWindow();
// Wait for a while to make sure no tab is opened.
yield new Promise(resolve => {
setTimeout(resolve, 1000);
});
is(win.gBrowser.tabs.length, 1,
"No captive portal tab should have been opened.");
yield BrowserTestUtils.closeWindow(win);
},
/**
* A portal is detected when a browser window has focus. A portal tab should be
* opened in the background in the focused browser window. If the portal is
* freed when the tab isn't focused, the tab should be closed automatically.
*/
function* test_detectedWithFocus(aSuccess) {
let win = RecentWindow.getMostRecentBrowserWindow();
let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
Services.obs.notifyObservers(null, "captive-portal-login", null);
let tab = yield p;
isnot(win.gBrowser.selectedTab, tab,
"The captive portal tab should be open in the background in the current window.");
freePortal(aSuccess);
is(win.gBrowser.tabs.length, 1,
"The portal tab should have been closed.");
},
/**
* A portal is detected when a browser window has focus. A portal tab should be
* opened in the background in the focused browser window. If the portal is
* freed when the tab has focus, the tab should be closed automatically.
*/
function* test_detectedWithFocus_selectedTab(aSuccess) {
let win = RecentWindow.getMostRecentBrowserWindow();
let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
Services.obs.notifyObservers(null, "captive-portal-login", null);
let tab = yield p;
isnot(win.gBrowser.selectedTab, tab,
"The captive portal tab should be open in the background in the current window.");
win.gBrowser.selectedTab = tab;
freePortal(aSuccess);
is(win.gBrowser.tabs.length, 1,
"The portal tab should have been closed.");
},
];
let singleRunTestCases = [
/**
* A portal is detected when there's no browser window,
* then a browser window is opened, and the portal is logged into
* and redirects to a different page. The portal tab should be added
* and focused when the window is opened, and left open after login
* since it redirected.
*/
function* test_detectedWithNoBrowserWindow_Redirect() {
yield portalDetectedNoBrowserWindow();
let win = yield openWindowAndWaitForPortalTab();
let browser = win.gBrowser.selectedTab.linkedBrowser;
let loadPromise =
BrowserTestUtils.browserLoaded(browser, false, CANONICAL_URL_REDIRECTED);
BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
yield loadPromise;
freePortal(true);
is(win.gBrowser.tabs.length, 2,
"The captive portal tab should not have been closed.");
yield BrowserTestUtils.closeWindow(win);
},
/**
* A portal is detected when a browser window has focus. A portal tab should be
* opened in the background in the focused browser window. If the portal is
* freed when the tab isn't focused, the tab should be closed automatically,
* even if the portal has redirected to a URL other than CANONICAL_URL.
*/
function* test_detectedWithFocus_redirectUnselectedTab() {
let win = RecentWindow.getMostRecentBrowserWindow();
let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
Services.obs.notifyObservers(null, "captive-portal-login", null);
let tab = yield p;
isnot(win.gBrowser.selectedTab, tab,
"The captive portal tab should be open in the background in the current window.");
let browser = tab.linkedBrowser;
let loadPromise =
BrowserTestUtils.browserLoaded(browser, false, CANONICAL_URL_REDIRECTED);
BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
yield loadPromise;
freePortal(true);
is(win.gBrowser.tabs.length, 1,
"The portal tab should have been closed.");
},
/**
* A portal is detected when a browser window has focus. A portal tab should be
* opened in the background in the focused browser window. If the portal is
* freed when the tab has focus, and it has redirected to another page, the
* tab should be kept open.
*/
function* test_detectedWithFocus_redirectSelectedTab() {
let win = RecentWindow.getMostRecentBrowserWindow();
let p = BrowserTestUtils.waitForNewTab(win.gBrowser, CANONICAL_URL);
Services.obs.notifyObservers(null, "captive-portal-login", null);
let tab = yield p;
isnot(win.gBrowser.selectedTab, tab,
"The captive portal tab should be open in the background in the current window.");
win.gBrowser.selectedTab = tab;
let browser = tab.linkedBrowser;
let loadPromise =
BrowserTestUtils.browserLoaded(browser, false, CANONICAL_URL_REDIRECTED);
BrowserTestUtils.loadURI(browser, CANONICAL_URL_REDIRECTED);
yield loadPromise;
freePortal(true);
is(win.gBrowser.tabs.length, 2,
"The portal tab should not have been closed.");
yield BrowserTestUtils.removeTab(tab);
},
];
for (let testcase of testCasesForBothSuccessAndAbort) {
add_task(testcase.bind(null, true));
add_task(testcase.bind(null, false));
}
for (let testcase of singleRunTestCases) {
add_task(testcase);
}

Просмотреть файл

@ -1962,6 +1962,6 @@ notification.pluginVulnerable > .notification-inner > .messageCloseButton:not(:h
overflow: hidden; overflow: hidden;
} }
.menuitem-iconic[command="Browser:NewUserContextTab"] > .menu-iconic-left > .menu-iconic-icon { .menuitem-iconic[usercontextid] > .menu-iconic-left > .menu-iconic-icon {
visibility: visible; visibility: visible;
} }

Просмотреть файл

@ -12,13 +12,17 @@
-moz-appearance: none !important; /* important, to override toolkit rule */ -moz-appearance: none !important; /* important, to override toolkit rule */
} }
#BMB_bookmarksPopup menupopup[placespopup=true] { #BMB_bookmarksPopup menupopup {
-moz-appearance: none;
background: var(--panel-arrowcontent-background);
color: var(--panel-arrowcontent-color);
border: var(--panel-arrowcontent-border);
margin-top: -6px; margin-top: -6px;
padding-top: 2px; padding-top: 1px;
} }
/* Add some space at the top because there are no headers: */ /* Add some space at the top because there are no headers: */
#BMB_bookmarksPopup menupopup[placespopup=true] > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox { #BMB_bookmarksPopup menupopup > hbox > .popup-internal-box > .arrowscrollbox-scrollbox > .scrollbox-innerbox {
padding-top: 4px; padding-top: 4px;
} }

Двоичные данные
browser/themes/linux/downloads/buttons.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 5.0 KiB

После

Ширина:  |  Высота:  |  Размер: 12 KiB

Просмотреть файл

@ -6,12 +6,14 @@
/*** Panel and outer controls ***/ /*** Panel and outer controls ***/
#downloadsFooter { #downloadsFooter,
#downloadsPanel-blockedSubview-buttons {
border-top: 1px solid ThreeDShadow; border-top: 1px solid ThreeDShadow;
background-image: linear-gradient(hsla(0,0%,0%,.15), hsla(0,0%,0%,.08) 6px); background-image: linear-gradient(hsla(0,0%,0%,.15), hsla(0,0%,0%,.08) 6px);
} }
#downloadsHistory { #downloadsHistory,
#downloadsPanel-blockedSubview-openButton {
color: -moz-nativehyperlinktext; color: -moz-nativehyperlinktext;
} }
@ -47,7 +49,8 @@
outline-offset: -1px; outline-offset: -1px;
} }
@notKeyfocus@ @itemFinished@[exists]:hover { @notKeyfocus@ @itemFinished@[exists]:hover,
@item@[showingsubview] {
border-radius: 3px; border-radius: 3px;
border-top: 1px solid hsla(0,0%,100%,.3); border-top: 1px solid hsla(0,0%,100%,.3);
border-bottom: 1px solid hsla(0,0%,0%,.2); border-bottom: 1px solid hsla(0,0%,0%,.2);
@ -57,6 +60,12 @@
cursor: pointer; cursor: pointer;
} }
@notKeyfocus@ @itemFinished@[exists][verdict="Malware"]:hover,
@item@[showingsubview][verdict="Malware"] {
background-color: hsl(4, 82%, 47%);
color: white;
}
/*** Button icons ***/ /*** Button icons ***/
.downloadButton.downloadIconCancel { .downloadButton.downloadIconCancel {
@ -106,3 +115,29 @@
@item@:hover .downloadButton.downloadIconRetry:active { @item@:hover .downloadButton.downloadIconRetry:active {
-moz-image-region: rect(32px, 64px, 48px, 48px); -moz-image-region: rect(32px, 64px, 48px, 48px);
} }
.downloadButton.downloadShowBlockedInfo {
-moz-image-region: rect(48px, 16px, 64px, 0px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo {
-moz-image-region: rect(48px, 32px, 64px, 16px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:hover {
-moz-image-region: rect(48px, 48px, 64px, 32px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:active {
-moz-image-region: rect(48px, 64px, 64px, 48px);
}
.downloadButton.downloadShowBlockedInfo:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 16px, 80px, 0px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 32px, 80px, 16px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:hover:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 48px, 80px, 32px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:active:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 64px, 80px, 48px);
}

Двоичные данные
browser/themes/osx/downloads/buttons.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 6.5 KiB

После

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
browser/themes/osx/downloads/buttons@2x.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 15 KiB

После

Ширина:  |  Высота:  |  Размер: 33 KiB

Просмотреть файл

@ -10,26 +10,45 @@
margin-top: -1px; margin-top: -1px;
} }
#downloadsFooter { #downloadsFooter,
#downloadsPanel-blockedSubview-buttons {
background: #e5e5e5; background: #e5e5e5;
border-top: 1px solid hsla(0,0%,0%,.1); border-top: 1px solid hsla(0,0%,0%,.1);
box-shadow: 0 -1px hsla(0,0%,100%,.5) inset, 0 1px 1px hsla(0,0%,0%,.03) inset; box-shadow: 0 -1px hsla(0,0%,100%,.5) inset, 0 1px 1px hsla(0,0%,0%,.03) inset;
}
#downloadsFooter,
#downloadsPanel-multiView .panel-subviews {
border-bottom-left-radius: 4px; border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px; border-bottom-right-radius: 4px;
} }
#downloadsHistory { #downloadsHistory,
#downloadsPanel-blockedSubview-buttons > button {
color: #808080; color: #808080;
} }
@keyfocus@ #downloadsSummary:focus, @keyfocus@ #downloadsSummary:focus,
@keyfocus@ #downloadsHistory:focus { @keyfocus@ #downloadsHistory:focus,
@keyfocus@ #downloadsPanel-blockedSubview-buttons > button:focus {
outline: 2px -moz-mac-focusring solid; outline: 2px -moz-mac-focusring solid;
outline-offset: -2px; outline-offset: -2px;
}
@keyfocus@ #downloadsSummary:focus,
@keyfocus@ #downloadsHistory:focus {
-moz-outline-radius-bottomleft: 4px; -moz-outline-radius-bottomleft: 4px;
-moz-outline-radius-bottomright: 4px; -moz-outline-radius-bottomright: 4px;
} }
@keyfocus@ #downloadsPanel-blockedSubview-deleteButton:-moz-locale-dir(ltr):focus {
-moz-outline-radius-bottomright: 4px;
}
@keyfocus@ #downloadsPanel-blockedSubview-deleteButton:-moz-locale-dir(rtl):focus {
-moz-outline-radius-bottomleft: 4px;
}
/*** List items and similar elements in the summary ***/ /*** List items and similar elements in the summary ***/
:root { :root {
@ -50,7 +69,8 @@
/*** Highlighted list items ***/ /*** Highlighted list items ***/
@keyfocus@ @itemFocused@, @keyfocus@ @itemFocused@,
@notKeyfocus@ @itemFinished@[exists]:hover { @notKeyfocus@ @itemFinished@[exists]:hover,
@item@[showingsubview] {
border-radius: 3px; border-radius: 3px;
border-top: 1px solid hsla(0,0%,100%,.2); border-top: 1px solid hsla(0,0%,100%,.2);
border-bottom: 1px solid hsla(0,0%,0%,.4); border-bottom: 1px solid hsla(0,0%,0%,.4);
@ -58,6 +78,18 @@
color: HighlightText; color: HighlightText;
} }
@item@[showingsubview] {
transition: background-color var(--panelui-subview-transition-duration),
color var(--panelui-subview-transition-duration);
}
@keyfocus@ @itemFocused@[verdict="Malware"],
@notKeyfocus@ @itemFinished@[exists][verdict="Malware"]:hover,
@item@[showingsubview][verdict="Malware"] {
background-color: hsl(4, 82%, 47%);
color: white;
}
@notKeyfocus@ @itemFinished@[exists]:hover { @notKeyfocus@ @itemFinished@[exists]:hover {
cursor: pointer; cursor: pointer;
} }
@ -145,6 +177,56 @@
-moz-image-region: rect(32px, 128px, 48px, 112px); -moz-image-region: rect(32px, 128px, 48px, 112px);
} }
.downloadButton.downloadShowBlockedInfo {
-moz-image-region: rect(48px, 16px, 64px, 0px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo {
-moz-image-region: rect(48px, 32px, 64px, 16px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:hover {
-moz-image-region: rect(48px, 48px, 64px, 32px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:active {
-moz-image-region: rect(48px, 64px, 64px, 48px);
}
@keyfocus@ @itemFocused@ .downloadButton.downloadShowBlockedInfo {
-moz-image-region: rect(48px, 80px, 64px, 64px);
}
@keyfocus@ @itemFocused@:hover .downloadButton.downloadShowBlockedInfo {
-moz-image-region: rect(48px, 96px, 64px, 80px);
}
@keyfocus@ @itemFocused@:hover .downloadButton.downloadShowBlockedInfo:hover {
-moz-image-region: rect(48px, 112px, 64px, 96px);
}
@keyfocus@ @itemFocused@:hover .downloadButton.downloadShowBlockedInfo:active {
-moz-image-region: rect(48px, 128px, 64px, 112px);
}
.downloadButton.downloadShowBlockedInfo:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 16px, 80px, 0px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 32px, 80px, 16px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:hover:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 48px, 80px, 32px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:active:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 64px, 80px, 48px);
}
@keyfocus@ @itemFocused@ .downloadButton.downloadShowBlockedInfo:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 80px, 80px, 64px);
}
@keyfocus@ @itemFocused@:hover .downloadButton.downloadShowBlockedInfo:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 96px, 80px, 80px);
}
@keyfocus@ @itemFocused@:hover .downloadButton.downloadShowBlockedInfo:hover:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 112px, 80px, 96px);
}
@keyfocus@ @itemFocused@:hover .downloadButton.downloadShowBlockedInfo:active:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 128px, 80px, 112px);
}
@media (min-resolution: 2dppx) { @media (min-resolution: 2dppx) {
.downloadButton { .downloadButton {
list-style-image: url("chrome://browser/skin/downloads/buttons@2x.png"); list-style-image: url("chrome://browser/skin/downloads/buttons@2x.png");
@ -234,4 +316,61 @@
@keyfocus@ @itemFocused@:hover .downloadButton.downloadIconRetry:active { @keyfocus@ @itemFocused@:hover .downloadButton.downloadIconRetry:active {
-moz-image-region: rect(64px, 256px, 96px, 224px); -moz-image-region: rect(64px, 256px, 96px, 224px);
} }
.downloadButton.downloadShowBlockedInfo {
-moz-image-region: rect(96px, 32px, 128px, 0px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo {
-moz-image-region: rect(96px, 64px, 128px, 32px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:hover {
-moz-image-region: rect(96px, 96px, 128px, 64px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:active {
-moz-image-region: rect(96px, 128px, 128px, 96px);
}
@keyfocus@ @itemFocused@ .downloadButton.downloadShowBlockedInfo {
-moz-image-region: rect(96px, 160px, 128px, 128px);
}
@keyfocus@ @itemFocused@:hover .downloadButton.downloadShowBlockedInfo {
-moz-image-region: rect(96px, 192px, 128px, 160px);
}
@keyfocus@ @itemFocused@:hover .downloadButton.downloadShowBlockedInfo:hover {
-moz-image-region: rect(96px, 224px, 128px, 192px);
}
@keyfocus@ @itemFocused@:hover .downloadButton.downloadShowBlockedInfo:active {
-moz-image-region: rect(96px, 256px, 128px, 224px);
}
.downloadButton.downloadShowBlockedInfo:-moz-locale-dir(rtl) {
-moz-image-region: rect(128px, 32px, 160px, 0px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:-moz-locale-dir(rtl) {
-moz-image-region: rect(128px, 64px, 160px, 32px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:hover:-moz-locale-dir(rtl) {
-moz-image-region: rect(128px, 96px, 160px, 64px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:active:-moz-locale-dir(rtl) {
-moz-image-region: rect(128px, 128px, 160px, 96px);
}
@keyfocus@ @itemFocused@ .downloadButton.downloadShowBlockedInfo:-moz-locale-dir(rtl) {
-moz-image-region: rect(128px, 160px, 160px, 128px);
}
@keyfocus@ @itemFocused@:hover .downloadButton.downloadShowBlockedInfo:-moz-locale-dir(rtl) {
-moz-image-region: rect(128px, 192px, 160px, 160px);
}
@keyfocus@ @itemFocused@:hover .downloadButton.downloadShowBlockedInfo:hover:-moz-locale-dir(rtl) {
-moz-image-region: rect(128px, 224px, 160px, 192px);
}
@keyfocus@ @itemFocused@:hover .downloadButton.downloadShowBlockedInfo:active:-moz-locale-dir(rtl) {
-moz-image-region: rect(128px, 256px, 160px, 224px);
}
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadButton {
list-style-image: url("chrome://browser/skin/customizableui/subView-arrow-back-inverted@2x.png");
}
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadButton:-moz-locale-dir(rtl) {
list-style-image: url("chrome://browser/skin/customizableui/subView-arrow-back-inverted-rtl@2x.png");
}
} }

Просмотреть файл

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
<style>
circle {
fill: #D92215;
}
rect {
fill: #fff;
}
</style>
<circle cx="8" cy="8" r="8" />
<rect x="3" y="6" width="10" height="4" rx=".5" ry=".5" />
</svg>

После

Ширина:  |  Высота:  |  Размер: 576 B

Просмотреть файл

@ -13,7 +13,8 @@
/*** Panel and outer controls ***/ /*** Panel and outer controls ***/
#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent { #downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent,
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
padding: 0; padding: 0;
} }
@ -23,17 +24,11 @@
color: inherit; color: inherit;
} }
#downloadsPanel:not([hasdownloads]) > #downloadsListBox {
display: none;
}
#downloadsPanel[hasdownloads] > #emptyDownloads {
display: none;
}
#emptyDownloads { #emptyDownloads {
padding: 10px 20px; padding: 10px 20px;
max-width: 40ch; /* The panel can be wider than this description after the blocked subview is
shown, so center the text. */
text-align: center;
} }
#downloadsSummary { #downloadsSummary {
@ -170,3 +165,74 @@ richlistitem[type="download"]:last-child {
.downloadButton > .button-box { .downloadButton > .button-box {
padding: 0; padding: 0;
} }
/*** Blocked subview ***/
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype=main] > .panel-subviews {
/* When the main view is showing, the shadow on the left edge of the subview is
barely visible on the right edge of the main view, so set it to none. */
box-shadow: none;
}
/* When the subview is showing, turn the download button into an arrow pointing
back to the main view. */
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadButton {
list-style-image: url("chrome://browser/skin/customizableui/subView-arrow-back-inverted.png");
}
#downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack[viewtype="subview"] .download-state[showingsubview] .downloadButton:-moz-locale-dir(rtl) {
list-style-image: url("chrome://browser/skin/customizableui/subView-arrow-back-inverted-rtl.png");
}
#downloadsPanel-blockedSubview {
background-image: url("chrome://browser/skin/warning.svg");
background-size: 32px 32px;
background-position: 16px 16px;
background-repeat: no-repeat;
}
#downloadsPanel-blockedSubview:-moz-locale-dir(rtl) {
background-position: calc(100% - 16px) 16px;
}
#downloadsPanel-blockedSubview[verdict=Malware] {
background-image: url("chrome://browser/skin/downloads/download-blocked.svg");
}
#downloadsPanel-blockedSubview-title {
margin-top: 16px;
margin-bottom: 16px;
font-size: calc(100% / var(--downloads-item-font-size-factor));
}
#downloadsPanel-blockedSubview-details1,
#downloadsPanel-blockedSubview-details2 {
font-size: calc(100% * var(--downloads-item-font-size-factor));
margin-bottom: 16px;
opacity: var(--downloads-item-details-opacity);
}
#downloadsPanel-blockedSubview-title,
#downloadsPanel-blockedSubview-details1,
#downloadsPanel-blockedSubview-details2 {
-moz-margin-start: 64px;
-moz-margin-end: 16px;
}
#downloadsPanel-blockedSubview-buttons {
height: 41px;
cursor: pointer;
}
#downloadsPanel-blockedSubview-buttons > button {
-moz-appearance: none;
padding: 0;
margin: 0;
background-color: transparent;
}
#downloadsPanel-blockedSubview-buttons > button[default] {
background-color: Highlight;
color: HighlightText;
}

Просмотреть файл

@ -47,11 +47,6 @@
background-color: var(--identity-box-selected-background-color); background-color: var(--identity-box-selected-background-color);
} }
#identity-box:hover > :not(#identity-icon),
#identity-box[open=true] > :not(#identity-icon) {
filter: grayscale(100%);
}
#urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity { #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
color: var(--identity-box-verified-color); color: var(--identity-box-verified-color);
} }
@ -106,6 +101,15 @@
list-style-image: url(chrome://browser/skin/identity-icon.svg#hover); list-style-image: url(chrome://browser/skin/identity-icon.svg#hover);
} }
#identity-box.grantedPermissions > #identity-icon {
list-style-image: url(chrome://browser/skin/identity-icon.svg#notice);
}
#identity-box.grantedPermissions:hover > #identity-icon,
#identity-box.grantedPermissions[open=true] > #identity-icon {
list-style-image: url(chrome://browser/skin/identity-icon.svg#notice-hover);
}
#urlbar[pageproxystate="valid"] > #identity-box.chromeUI > #identity-icon { #urlbar[pageproxystate="valid"] > #identity-box.chromeUI > #identity-icon {
list-style-image: url(chrome://branding/content/identity-icons-brand.svg); list-style-image: url(chrome://branding/content/identity-icons-brand.svg);
} }

Просмотреть файл

@ -3,29 +3,34 @@
- License, v. 2.0. If a copy of the MPL was not distributed with this - License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="48" height="16" viewBox="0 0 32 16"> width="64" height="16" viewBox="0 0 64 16">
<defs> <defs>
<circle id="shape-circle-base" cx="8" cy="8" r="7" /> <style>
<g id="shape-i"> path {
<circle cx="8" cy="5" r="1" /> fill-rule: evenodd;
<rect x="7" y="7" width="2" height="5" rx="1" ry="1" /> fill: #999999;
</g> }
<mask id="mask-ring-cutout"> </style>
<rect width="16" height="16" fill="#000" />
<use xlink:href="#shape-circle-base" fill="#fff" />
<circle cx="8" cy="8" r="6" fill="#000" />
</mask>
</defs> </defs>
<view id="normal" viewBox="0 0 16 16"/> <view id="normal" viewBox="0 0 16 16"/>
<g> <g>
<use xlink:href="#shape-circle-base" mask="url(#mask-ring-cutout)" fill="#999" /> <path d="M128,193a7,7,0,1,1,7-7A7,7,0,0,1,128,193Zm0-13a6,6,0,1,0,6,6A6,6,0,0,0,128,180Zm0,10a1,1,0,0,1-1-1v-3a1,1,0,0,1,2,0v3A1,1,0,0,1,128,190Zm0-6a1,1,0,1,1,1-1A1,1,0,0,1,128,184Z" transform="translate(-120 -178)"/>
<use xlink:href="#shape-i" fill="#999" />
</g> </g>
<view id="hover" viewBox="16 0 16 16"/> <view id="hover" viewBox="16 0 16 16"/>
<g transform="translate(16)"> <g transform="translate(16)">
<use xlink:href="#shape-circle-base" fill="#4c9ed9" /> <path d="M102,179a7,7,0,1,1-7,7A7,7,0,0,1,102,179Zm0,3a1,1,0,1,1-1,1A1,1,0,0,1,102,182Zm0,3a1,1,0,0,1,1,1v3a1,1,0,0,1-2,0v-3A1,1,0,0,1,102,185Z" transform="translate(-94 -178)"/>
<use xlink:href="#shape-i" fill="#fff" />
</g> </g>
<view id="notice" viewBox="32 0 16 16"/>
<g transform="translate(32)">
<path d="M133.5,202a2.5,2.5,0,1,1,2.5-2.5A2.5,2.5,0,0,1,133.5,202Zm-5.5,1a1,1,0,1,1,1-1A1,1,0,0,1,128,203Zm1,5a1,1,0,0,1-2,0v-3a1,1,0,0,1,2,0v3Zm-1-9a6.08,6.08,0,1,0,5.629,3.987,3.452,3.452,0,0,0,.984-0.185A6.9,6.9,0,0,1,135,205a7,7,0,1,1-7-7,6.9,6.9,0,0,1,2.2.387,3.452,3.452,0,0,0-.185.984A5.951,5.951,0,0,0,128,199Z" transform="translate(-120 -197)"/>
</g>
<view id="notice-hover" viewBox="48 0 16 16"/>
<g transform="translate(48)">
<path d="M107.5,202a2.5,2.5,0,1,1,2.5-2.5A2.5,2.5,0,0,1,107.5,202Zm0,1.039a3.5,3.5,0,0,0,1.125-.2,7.124,7.124,0,1,1-4.464-4.464,3.5,3.5,0,0,0-.2,1.125A3.54,3.54,0,0,0,107.5,203.039ZM102,201a1,1,0,1,0,1,1A1,1,0,0,0,102,201Zm1,4a1,1,0,0,0-2,0v3a1,1,0,0,0,2,0v-3Z" transform="translate(-94 -197)"/>
</g>
</svg> </svg>

До

Ширина:  |  Высота:  |  Размер: 1.2 KiB

После

Ширина:  |  Высота:  |  Размер: 1.9 KiB

Просмотреть файл

@ -53,6 +53,7 @@
skin/classic/browser/customizableui/whimsy@2x.png (../shared/customizableui/whimsy@2x.png) skin/classic/browser/customizableui/whimsy@2x.png (../shared/customizableui/whimsy@2x.png)
skin/classic/browser/download-blocked.svg (../shared/download-blocked.svg) skin/classic/browser/download-blocked.svg (../shared/download-blocked.svg)
skin/classic/browser/downloads/contentAreaDownloadsView.css (../shared/downloads/contentAreaDownloadsView.css) skin/classic/browser/downloads/contentAreaDownloadsView.css (../shared/downloads/contentAreaDownloadsView.css)
skin/classic/browser/downloads/download-blocked.svg (../shared/downloads/download-blocked.svg)
skin/classic/browser/drm-icon.svg (../shared/drm-icon.svg) skin/classic/browser/drm-icon.svg (../shared/drm-icon.svg)
skin/classic/browser/filters.svg (../shared/filters.svg) skin/classic/browser/filters.svg (../shared/filters.svg)
skin/classic/browser/fullscreen/insecure.svg (../shared/fullscreen/insecure.svg) skin/classic/browser/fullscreen/insecure.svg (../shared/fullscreen/insecure.svg)

Двоичные данные
browser/themes/windows/downloads/buttons-XP.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 6.7 KiB

После

Ширина:  |  Высота:  |  Размер: 14 KiB

Двоичные данные
browser/themes/windows/downloads/buttons.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 4.0 KiB

После

Ширина:  |  Высота:  |  Размер: 42 KiB

Просмотреть файл

@ -6,18 +6,21 @@
/*** Panel and outer controls ***/ /*** Panel and outer controls ***/
#downloadsFooter { #downloadsFooter,
#downloadsPanel-blockedSubview-openButton {
background-color: hsla(210,4%,10%,.04); background-color: hsla(210,4%,10%,.04);
box-shadow: 0 1px 0 hsla(210,4%,10%,.08) inset; box-shadow: 0 1px 0 hsla(210,4%,10%,.08) inset;
transition-duration: 150ms; transition-duration: 150ms;
transition-property: background-color; transition-property: background-color;
} }
#downloadsFooter:hover { #downloadsFooter:hover,
#downloadsPanel-blockedSubview-openButton:hover {
background-color: hsla(210,4%,10%,.05); background-color: hsla(210,4%,10%,.05);
} }
#downloadsFooter:hover:active { #downloadsFooter:hover:active,
#downloadsPanel-blockedSubview-openButton:hover:active {
background-color: hsla(210,4%,10%,.1); background-color: hsla(210,4%,10%,.1);
box-shadow: 0 2px 0 0 hsla(210,4%,10%,.1) inset; box-shadow: 0 2px 0 0 hsla(210,4%,10%,.1) inset;
} }
@ -25,12 +28,14 @@
@media (-moz-os-version: windows-xp), @media (-moz-os-version: windows-xp),
(-moz-os-version: windows-vista), (-moz-os-version: windows-vista),
(-moz-os-version: windows-win7) { (-moz-os-version: windows-win7) {
#downloadsHistory { #downloadsHistory,
#downloadsPanel-blockedSubview-buttons > button {
color: -moz-nativehyperlinktext; color: -moz-nativehyperlinktext;
} }
@media (-moz-windows-default-theme) { @media (-moz-windows-default-theme) {
#downloadsFooter { #downloadsFooter,
#downloadsPanel-multiView .panel-subviews {
border-bottom-left-radius: 3px; border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px; border-bottom-right-radius: 3px;
transition-duration: 0s; transition-duration: 0s;
@ -38,7 +43,10 @@
#downloadsFooter, #downloadsFooter,
#downloadsFooter:hover, #downloadsFooter:hover,
#downloadsFooter:hover:active { #downloadsFooter:hover:active,
#downloadsPanel-blockedSubview-openButton,
#downloadsPanel-blockedSubview-openButton:hover,
#downloadsPanel-blockedSubview-openButton:hover:active {
background-color: #f1f5fb; background-color: #f1f5fb;
box-shadow: 0px 1px 2px rgb(204,214,234) inset; box-shadow: 0px 1px 2px rgb(204,214,234) inset;
} }
@ -46,7 +54,10 @@
@media (-moz-os-version: windows-xp) { @media (-moz-os-version: windows-xp) {
#downloadsFooter, #downloadsFooter,
#downloadsFooter:hover, #downloadsFooter:hover,
#downloadsFooter:hover:active { #downloadsFooter:hover:active,
#downloadsPanel-blockedSubview-openButton,
#downloadsPanel-blockedSubview-openButton:hover,
#downloadsPanel-blockedSubview-openButton:hover:active {
background-color: hsla(216,45%,88%,.98); background-color: hsla(216,45%,88%,.98);
} }
} }
@ -63,7 +74,8 @@
outline-offset: -5px; outline-offset: -5px;
} }
#downloadsHistory > .button-box { #downloadsHistory > .button-box,
#downloadsPanel-blockedSubview-buttons > button > .button-box {
/* Hide the border so we don't display an inner focus ring. */ /* Hide the border so we don't display an inner focus ring. */
border: none; border: none;
} }
@ -116,6 +128,20 @@
box-shadow: 0 1px 0 0 hsla(210,4%,10%,.05) inset; box-shadow: 0 1px 0 0 hsla(210,4%,10%,.05) inset;
} }
@item@[showingsubview] {
background-color: Highlight;
color: HighlightText;
transition: background-color var(--panelui-subview-transition-duration),
color var(--panelui-subview-transition-duration);
}
@notKeyfocus@ @itemFinished@[exists][verdict="Malware"]:hover,
@notKeyfocus@ @itemFinished@[exists][verdict="Malware"]:hover:active,
@item@[showingsubview][verdict="Malware"] {
background-color: hsl(4, 82%, 47%);
color: white;
}
@media (-moz-os-version: windows-xp), @media (-moz-os-version: windows-xp),
(-moz-os-version: windows-vista), (-moz-os-version: windows-vista),
(-moz-os-version: windows-win7) { (-moz-os-version: windows-win7) {
@ -206,3 +232,29 @@
@item@:hover .downloadButton.downloadIconRetry:active { @item@:hover .downloadButton.downloadIconRetry:active {
-moz-image-region: rect(32px, 64px, 48px, 48px); -moz-image-region: rect(32px, 64px, 48px, 48px);
} }
.downloadButton.downloadShowBlockedInfo {
-moz-image-region: rect(48px, 16px, 64px, 0px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo {
-moz-image-region: rect(48px, 32px, 64px, 16px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:hover {
-moz-image-region: rect(48px, 48px, 64px, 32px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:active {
-moz-image-region: rect(48px, 64px, 64px, 48px);
}
.downloadButton.downloadShowBlockedInfo:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 16px, 80px, 0px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 32px, 80px, 16px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:hover:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 48px, 80px, 32px);
}
@item@:hover .downloadButton.downloadShowBlockedInfo:active:-moz-locale-dir(rtl) {
-moz-image-region: rect(64px, 64px, 80px, 48px);
}

Просмотреть файл

@ -815,10 +815,6 @@ HTMLBreadcrumbs.prototype = {
return; return;
} }
if (reason !== "markupmutation") {
this.inspector.hideNodeMenu();
}
let hasInterestingMutations = this._hasInterestingMutations(mutations); let hasInterestingMutations = this._hasInterestingMutations(mutations);
if (reason === "markupmutation" && !hasInterestingMutations) { if (reason === "markupmutation" && !hasInterestingMutations) {
return; return;

Просмотреть файл

@ -20,6 +20,9 @@ var {Task} = require("devtools/shared/task");
const {initCssProperties} = require("devtools/shared/fronts/css-properties"); const {initCssProperties} = require("devtools/shared/fronts/css-properties");
const nodeConstants = require("devtools/shared/dom-node-constants"); const nodeConstants = require("devtools/shared/dom-node-constants");
const Menu = require("devtools/client/framework/menu");
const MenuItem = require("devtools/client/framework/menu-item");
loader.lazyRequireGetter(this, "CSS", "CSS"); loader.lazyRequireGetter(this, "CSS", "CSS");
loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true); loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
@ -90,8 +93,7 @@ function InspectorPanel(iframeWindow, toolbox) {
this._onBeforeNavigate = this._onBeforeNavigate.bind(this); this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
this.onNewRoot = this.onNewRoot.bind(this); this.onNewRoot = this.onNewRoot.bind(this);
this._setupNodeMenu = this._setupNodeMenu.bind(this); this._onContextMenu = this._onContextMenu.bind(this);
this._resetNodeMenu = this._resetNodeMenu.bind(this);
this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this); this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
this.onNewSelection = this.onNewSelection.bind(this); this.onNewSelection = this.onNewSelection.bind(this);
this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this); this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
@ -107,6 +109,7 @@ function InspectorPanel(iframeWindow, toolbox) {
this.addNodeButton.addEventListener("click", this.addNode); this.addNodeButton.addEventListener("click", this.addNode);
this._target.on("will-navigate", this._onBeforeNavigate); this._target.on("will-navigate", this._onBeforeNavigate);
this._detectingActorFeatures = this._detectActorFeatures();
EventEmitter.decorate(this); EventEmitter.decorate(this);
} }
@ -162,16 +165,32 @@ InspectorPanel.prototype = {
return this._target.client.traits.pasteHTML; return this._target.client.traits.pasteHTML;
}, },
/**
* Figure out what features the backend supports
*/
_detectActorFeatures: function () {
this._supportsDuplicateNode = false;
this._supportsScrollIntoView = false;
this._supportsResolveRelativeURL = false;
return promise.all([
this._target.actorHasMethod("domwalker", "duplicateNode").then(value => {
this._supportsDuplicateNode = value;
}).catch(e => console.error(e)),
this._target.actorHasMethod("domnode", "scrollIntoView").then(value => {
this._supportsScrollIntoView = value;
}).catch(e => console.error(e)),
this._target.actorHasMethod("inspector", "resolveRelativeURL").then(value => {
this._supportsResolveRelativeURL = value;
}).catch(e => console.error(e)),
]);
},
_deferredOpen: function (defaultSelection) { _deferredOpen: function (defaultSelection) {
let deferred = promise.defer(); let deferred = promise.defer();
this.walker.on("new-root", this.onNewRoot); this.walker.on("new-root", this.onNewRoot);
this.nodemenu = this.panelDoc.getElementById("inspector-node-popup");
this.lastNodemenuItem = this.nodemenu.lastChild;
this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
this.selection.on("new-node-front", this.onNewSelection); this.selection.on("new-node-front", this.onNewSelection);
this.selection.on("before-new-node-front", this.onBeforeNewSelection); this.selection.on("before-new-node-front", this.onBeforeNewSelection);
this.selection.on("detached-front", this.onDetached); this.selection.on("detached-front", this.onDetached);
@ -668,9 +687,6 @@ InspectorPanel.prototype = {
this.sidebar = null; this.sidebar = null;
this.addNodeButton.removeEventListener("click", this.addNode); this.addNodeButton.removeEventListener("click", this.addNode);
this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
this.breadcrumbs.destroy(); this.breadcrumbs.destroy();
this._paneToggleButton.removeEventListener("mousedown", this._paneToggleButton.removeEventListener("mousedown",
this.onPaneToggleButtonClicked); this.onPaneToggleButtonClicked);
@ -685,8 +701,6 @@ InspectorPanel.prototype = {
this.panelDoc = null; this.panelDoc = null;
this.panelWin = null; this.panelWin = null;
this.breadcrumbs = null; this.breadcrumbs = null;
this.lastNodemenuItem = null;
this.nodemenu = null;
this._toolbox = null; this._toolbox = null;
this.search.destroy(); this.search.destroy();
this.search = null; this.search = null;
@ -701,22 +715,6 @@ InspectorPanel.prototype = {
return this._panelDestroyer; return this._panelDestroyer;
}, },
/**
* Show the node menu.
*/
showNodeMenu: function (button, position, extraItems) {
if (extraItems) {
for (let item of extraItems) {
this.nodemenu.appendChild(item);
}
}
this.nodemenu.openPopup(button, position, 0, 0, true, false);
},
hideNodeMenu: function () {
this.nodemenu.hidePopup();
},
/** /**
* Returns the clipboard content if it is appropriate for pasting * Returns the clipboard content if it is appropriate for pasting
* into the current node's outer HTML, otherwise returns null. * into the current node's outer HTML, otherwise returns null.
@ -733,14 +731,21 @@ InspectorPanel.prototype = {
return null; return null;
}, },
/** _onContextMenu: function (e) {
* Update, enable, disable, hide, show any menu item depending on the current e.preventDefault();
* element. this._openMenu({
*/ screenX: e.screenX,
_setupNodeMenu: function (event) { screenY: e.screenY,
target: e.target,
});
},
_openMenu: function ({ target, screenX = 0, screenY = 0 } = { }) {
let markupContainer = this.markup.getContainer(this.selection.nodeFront); let markupContainer = this.markup.getContainer(this.selection.nodeFront);
this.nodeMenuTriggerInfo =
markupContainer.editor.getInfoAtNode(event.target.triggerNode); this.contextMenuTarget = target;
this.nodeMenuTriggerInfo = markupContainer &&
markupContainer.editor.getInfoAtNode(target);
let isSelectionElement = this.selection.isElementNode() && let isSelectionElement = this.selection.isElementNode() &&
!this.selection.isPseudoElementNode(); !this.selection.isPseudoElementNode();
@ -753,246 +758,324 @@ InspectorPanel.prototype = {
this.canGetUniqueSelector && this.canGetUniqueSelector &&
this.selection.nodeFront.isTreeDisplayed; this.selection.nodeFront.isTreeDisplayed;
let menu = new Menu();
menu.append(new MenuItem({
id: "node-menu-edithtml",
label: strings.GetStringFromName("inspectorHTMLEdit.label"),
accesskey: strings.GetStringFromName("inspectorHTMLEdit.accesskey"),
disabled: !isEditableElement || !this.isOuterHTMLEditable,
click: () => this.editHTML(),
}));
menu.append(new MenuItem({
id: "node-menu-add",
label: strings.GetStringFromName("inspectorAddNode.label"),
accesskey: strings.GetStringFromName("inspectorAddNode.accesskey"),
disabled: !this.canAddHTMLChild(),
click: () => this.addNode(),
}));
menu.append(new MenuItem({
id: "node-menu-duplicatenode",
label: strings.GetStringFromName("inspectorDuplicateNode.label"),
hidden: !this._supportsDuplicateNode,
disabled: !isDuplicatableElement,
click: () => this.duplicateNode(),
}));
menu.append(new MenuItem({
id: "node-menu-delete",
label: strings.GetStringFromName("inspectorHTMLDelete.label"),
accesskey: strings.GetStringFromName("inspectorHTMLDelete.accesskey"),
disabled: !isEditableElement,
click: () => this.deleteNode(),
}));
menu.append(new MenuItem({
label: strings.GetStringFromName("inspectorAttributesSubmenu.label"),
accesskey:
strings.GetStringFromName("inspectorAttributesSubmenu.accesskey"),
submenu: this._getAttributesSubmenu(isEditableElement),
}));
menu.append(new MenuItem({
type: "separator",
}));
// Set the pseudo classes // Set the pseudo classes
for (let name of ["hover", "active", "focus"]) { for (let name of ["hover", "active", "focus"]) {
let menu = this.panelDoc.getElementById("node-menu-pseudo-" + name); let menuitem = new MenuItem({
id: "node-menu-pseudo-" + name,
label: name,
type: "checkbox",
click: this.togglePseudoClass.bind(this, ":" + name),
});
if (isSelectionElement) { if (isSelectionElement) {
let checked = this.selection.nodeFront.hasPseudoClassLock(":" + name); let checked = this.selection.nodeFront.hasPseudoClassLock(":" + name);
menu.setAttribute("checked", checked); menuitem.checked = checked;
menu.removeAttribute("disabled");
} else { } else {
menu.setAttribute("disabled", "true"); menuitem.disabled = true;
} }
menu.append(menuitem);
} }
// Disable delete item if needed menu.append(new MenuItem({
let deleteNode = this.panelDoc.getElementById("node-menu-delete"); type: "separator",
if (isEditableElement) { }));
deleteNode.removeAttribute("disabled");
} else { let copySubmenu = new Menu();
deleteNode.setAttribute("disabled", "true"); copySubmenu.append(new MenuItem({
id: "node-menu-copyinner",
label: strings.GetStringFromName("inspectorCopyInnerHTML.label"),
accesskey: strings.GetStringFromName("inspectorCopyInnerHTML.accesskey"),
disabled: !isSelectionElement,
click: () => this.copyInnerHTML(),
}));
copySubmenu.append(new MenuItem({
id: "node-menu-copyouter",
label: strings.GetStringFromName("inspectorCopyOuterHTML.label"),
accesskey: strings.GetStringFromName("inspectorCopyOuterHTML.accesskey"),
disabled: !isSelectionElement,
click: () => this.copyOuterHTML(),
}));
copySubmenu.append(new MenuItem({
id: "node-menu-copyuniqueselector",
label: strings.GetStringFromName("inspectorCopyCSSSelector.label"),
accesskey:
strings.GetStringFromName("inspectorCopyCSSSelector.accesskey"),
disabled: !isSelectionElement,
hidden: !this.canGetUniqueSelector,
click: () => this.copyUniqueSelector(),
}));
copySubmenu.append(new MenuItem({
id: "node-menu-copyimagedatauri",
label: strings.GetStringFromName("inspectorImageDataUri.label"),
disabled: !isSelectionElement || !markupContainer ||
!markupContainer.isPreviewable(),
click: () => this.copyImageDataUri(),
}));
menu.append(new MenuItem({
label: strings.GetStringFromName("inspectorCopyHTMLSubmenu.label"),
submenu: copySubmenu,
}));
menu.append(new MenuItem({
label: strings.GetStringFromName("inspectorPasteHTMLSubmenu.label"),
submenu: this._getPasteSubmenu(isEditableElement),
}));
menu.append(new MenuItem({
type: "separator",
}));
let isNodeWithChildren = this.selection.isNode() &&
markupContainer.hasChildren;
menu.append(new MenuItem({
id: "node-menu-expand",
label: strings.GetStringFromName("inspectorExpandNode.label"),
disabled: !isNodeWithChildren || markupContainer.expanded,
click: () => this.expandNode(),
}));
menu.append(new MenuItem({
id: "node-menu-collapse",
label: strings.GetStringFromName("inspectorCollapseNode.label"),
disabled: !isNodeWithChildren || !markupContainer.expanded,
click: () => this.collapseNode(),
}));
menu.append(new MenuItem({
type: "separator",
}));
menu.append(new MenuItem({
id: "node-menu-scrollnodeintoview",
label: strings.GetStringFromName("inspectorScrollNodeIntoView.label"),
accesskey:
strings.GetStringFromName("inspectorScrollNodeIntoView.accesskey"),
hidden: !this._supportsScrollIntoView,
disabled: !isSelectionElement,
click: () => this.scrollNodeIntoView(),
}));
menu.append(new MenuItem({
id: "node-menu-screenshotnode",
label: strings.GetStringFromName("inspectorScreenshotNode.label"),
disabled: !isScreenshotable,
click: () => this.screenshotNode(),
}));
menu.append(new MenuItem({
id: "node-menu-useinconsole",
label: strings.GetStringFromName("inspectorUseInConsole.label"),
click: () => this.useInConsole(),
}));
menu.append(new MenuItem({
id: "node-menu-showdomproperties",
label: strings.GetStringFromName("inspectorShowDOMProperties.label"),
click: () => this.showDOMProperties(),
}));
let nodeLinkMenuItems = this._getNodeLinkMenuItems();
if (nodeLinkMenuItems.filter(item => item.visible).length > 0) {
menu.append(new MenuItem({
id: "node-menu-link-separator",
type: "separator",
}));
} }
// Disable add item if needed for (let menuitem of nodeLinkMenuItems) {
let addNode = this.panelDoc.getElementById("node-menu-add"); menu.append(menuitem);
if (this.canAddHTMLChild()) {
addNode.removeAttribute("disabled");
} else {
addNode.setAttribute("disabled", "true");
} }
// Disable / enable "Copy Unique Selector", "Copy inner HTML", menu.popup(screenX, screenY, this._toolbox);
// "Copy outer HTML", "Scroll Into View" & "Screenshot Node" as appropriate return menu;
let unique = this.panelDoc.getElementById("node-menu-copyuniqueselector");
let screenshot = this.panelDoc.getElementById("node-menu-screenshotnode");
let duplicateNode = this.panelDoc.getElementById("node-menu-duplicatenode");
let copyInnerHTML = this.panelDoc.getElementById("node-menu-copyinner");
let copyOuterHTML = this.panelDoc.getElementById("node-menu-copyouter");
let scrollIntoView = this.panelDoc.getElementById("node-menu-scrollnodeintoview");
let expandAll = this.panelDoc.getElementById("node-menu-expand");
let collapse = this.panelDoc.getElementById("node-menu-collapse");
expandAll.setAttribute("disabled", "true");
collapse.setAttribute("disabled", "true");
if (this.selection.isNode() && markupContainer.hasChildren) {
if (markupContainer.expanded) {
collapse.removeAttribute("disabled");
}
expandAll.removeAttribute("disabled");
}
this._target.actorHasMethod("domwalker", "duplicateNode").then(value => {
duplicateNode.hidden = !value;
});
this._target.actorHasMethod("domnode", "scrollIntoView").then(value => {
scrollIntoView.hidden = !value;
});
if (isDuplicatableElement) {
duplicateNode.removeAttribute("disabled");
} else {
duplicateNode.setAttribute("disabled", "true");
}
if (isSelectionElement) {
unique.removeAttribute("disabled");
copyInnerHTML.removeAttribute("disabled");
copyOuterHTML.removeAttribute("disabled");
scrollIntoView.removeAttribute("disabled");
} else {
unique.setAttribute("disabled", "true");
copyInnerHTML.setAttribute("disabled", "true");
copyOuterHTML.setAttribute("disabled", "true");
scrollIntoView.setAttribute("disabled", "true");
}
if (!this.canGetUniqueSelector) {
unique.hidden = true;
}
if (isScreenshotable) {
screenshot.removeAttribute("disabled");
} else {
screenshot.setAttribute("disabled", "true");
}
// Enable/Disable the link open/copy items.
this._setupNodeLinkMenu();
// Enable the "edit HTML" item if the selection is an element and the root
// actor has the appropriate trait (isOuterHTMLEditable)
let editHTML = this.panelDoc.getElementById("node-menu-edithtml");
if (isEditableElement && this.isOuterHTMLEditable) {
editHTML.removeAttribute("disabled");
} else {
editHTML.setAttribute("disabled", "true");
}
let pasteOuterHTML = this.panelDoc.getElementById("node-menu-pasteouterhtml");
let pasteInnerHTML = this.panelDoc.getElementById("node-menu-pasteinnerhtml");
let pasteBefore = this.panelDoc.getElementById("node-menu-pastebefore");
let pasteAfter = this.panelDoc.getElementById("node-menu-pasteafter");
let pasteFirstChild = this.panelDoc.getElementById("node-menu-pastefirstchild");
let pasteLastChild = this.panelDoc.getElementById("node-menu-pastelastchild");
// Is the clipboard content appropriate? Is the element editable?
if (isEditableElement && this._getClipboardContentForPaste()) {
pasteInnerHTML.disabled = !this.canPasteInnerOrAdjacentHTML;
// Enable the "paste outer HTML" item if the selection is an element and
// the root actor has the appropriate trait (isOuterHTMLEditable).
pasteOuterHTML.disabled = !this.isOuterHTMLEditable;
// Don't paste before / after a root or a BODY or a HEAD element.
pasteBefore.disabled = pasteAfter.disabled =
!this.canPasteInnerOrAdjacentHTML || this.selection.isRoot() ||
this.selection.isBodyNode() || this.selection.isHeadNode();
// Don't paste as a first / last child of a HTML document element.
pasteFirstChild.disabled = pasteLastChild.disabled =
!this.canPasteInnerOrAdjacentHTML || (this.selection.isHTMLNode() &&
this.selection.isRoot());
} else {
pasteOuterHTML.disabled = true;
pasteInnerHTML.disabled = true;
pasteBefore.disabled = true;
pasteAfter.disabled = true;
pasteFirstChild.disabled = true;
pasteLastChild.disabled = true;
}
// Enable the "copy image data-uri" item if the selection is previewable
// which essentially checks if it's an image or canvas tag
let copyImageData = this.panelDoc.getElementById("node-menu-copyimagedatauri");
if (isSelectionElement && markupContainer && markupContainer.isPreviewable()) {
copyImageData.removeAttribute("disabled");
} else {
copyImageData.setAttribute("disabled", "true");
}
// Enable / disable "Add Attribute", "Edit Attribute"
// and "Remove Attribute" items
this._setupAttributeMenu(isEditableElement);
}, },
_setupAttributeMenu: function (isEditableElement) { _getPasteSubmenu: function (isEditableElement) {
let addAttribute = this.panelDoc.getElementById("node-menu-add-attribute"); let isPasteable = isEditableElement && this._getClipboardContentForPaste();
let editAttribute = this.panelDoc.getElementById("node-menu-edit-attribute"); let disableAdjacentPaste = !isPasteable ||
let removeAttribute = this.panelDoc.getElementById("node-menu-remove-attribute"); !this.canPasteInnerOrAdjacentHTML || this.selection.isRoot() ||
this.selection.isBodyNode() || this.selection.isHeadNode();
let disableFirstLastPaste = !isPasteable ||
!this.canPasteInnerOrAdjacentHTML || (this.selection.isHTMLNode() &&
this.selection.isRoot());
let pasteSubmenu = new Menu();
pasteSubmenu.append(new MenuItem({
id: "node-menu-pasteinnerhtml",
label: strings.GetStringFromName("inspectorPasteInnerHTML.label"),
accesskey: strings.GetStringFromName("inspectorPasteInnerHTML.accesskey"),
disabled: !isPasteable || !this.canPasteInnerOrAdjacentHTML,
click: () => this.pasteInnerHTML(),
}));
pasteSubmenu.append(new MenuItem({
id: "node-menu-pasteouterhtml",
label: strings.GetStringFromName("inspectorPasteOuterHTML.label"),
accesskey: strings.GetStringFromName("inspectorPasteOuterHTML.accesskey"),
disabled: !isPasteable || !this.isOuterHTMLEditable,
click: () => this.pasteOuterHTML(),
}));
pasteSubmenu.append(new MenuItem({
id: "node-menu-pastebefore",
label: strings.GetStringFromName("inspectorHTMLPasteBefore.label"),
accesskey:
strings.GetStringFromName("inspectorHTMLPasteBefore.accesskey"),
disabled: disableAdjacentPaste,
click: () => this.pasteAdjacentHTML("beforeBegin"),
}));
pasteSubmenu.append(new MenuItem({
id: "node-menu-pasteafter",
label: strings.GetStringFromName("inspectorHTMLPasteAfter.label"),
accesskey:
strings.GetStringFromName("inspectorHTMLPasteAfter.accesskey"),
disabled: disableAdjacentPaste,
click: () => this.pasteAdjacentHTML("afterEnd"),
}));
pasteSubmenu.append(new MenuItem({
id: "node-menu-pastefirstchild",
label: strings.GetStringFromName("inspectorHTMLPasteFirstChild.label"),
accesskey:
strings.GetStringFromName("inspectorHTMLPasteFirstChild.accesskey"),
disabled: disableFirstLastPaste,
click: () => this.pasteAdjacentHTML("afterBegin"),
}));
pasteSubmenu.append(new MenuItem({
id: "node-menu-pastelastchild",
label: strings.GetStringFromName("inspectorHTMLPasteLastChild.label"),
accesskey:
strings.GetStringFromName("inspectorHTMLPasteLastChild.accesskey"),
disabled: disableFirstLastPaste,
click: () => this.pasteAdjacentHTML("beforeEnd"),
}));
return pasteSubmenu;
},
_getAttributesSubmenu: function (isEditableElement) {
let attributesSubmenu = new Menu();
let nodeInfo = this.nodeMenuTriggerInfo; let nodeInfo = this.nodeMenuTriggerInfo;
let isAttributeClicked = isEditableElement && nodeInfo &&
nodeInfo.type === "attribute";
// Enable "Add Attribute" for all editable elements attributesSubmenu.append(new MenuItem({
if (isEditableElement) { id: "node-menu-add-attribute",
addAttribute.removeAttribute("disabled"); label: strings.GetStringFromName("inspectorAddAttribute.label"),
} else { accesskey: strings.GetStringFromName("inspectorAddAttribute.accesskey"),
addAttribute.setAttribute("disabled", "true"); disabled: !isEditableElement,
} click: () => this.onAddAttribute(),
}));
attributesSubmenu.append(new MenuItem({
id: "node-menu-edit-attribute",
label: strings.formatStringFromName("inspectorEditAttribute.label",
[isAttributeClicked ? `"${nodeInfo.name}"` : ""], 1),
accesskey: strings.GetStringFromName("inspectorEditAttribute.accesskey"),
disabled: !isAttributeClicked,
click: () => this.onEditAttribute(),
}));
// Enable "Edit Attribute" and "Remove Attribute" only on attribute click attributesSubmenu.append(new MenuItem({
if (isEditableElement && nodeInfo && nodeInfo.type === "attribute") { id: "node-menu-remove-attribute",
editAttribute.removeAttribute("disabled"); label: strings.formatStringFromName("inspectorRemoveAttribute.label",
editAttribute.setAttribute("label", [isAttributeClicked ? `"${nodeInfo.name}"` : ""], 1),
strings.formatStringFromName( accesskey:
"inspector.menu.editAttribute.label", [`"${nodeInfo.name}"`], 1)); strings.GetStringFromName("inspectorRemoveAttribute.accesskey"),
disabled: !isAttributeClicked,
click: () => this.onRemoveAttribute(),
}));
removeAttribute.removeAttribute("disabled"); return attributesSubmenu;
removeAttribute.setAttribute("label",
strings.formatStringFromName(
"inspector.menu.removeAttribute.label", [`"${nodeInfo.name}"`], 1));
} else {
editAttribute.setAttribute("disabled", "true");
editAttribute.setAttribute("label",
strings.formatStringFromName(
"inspector.menu.editAttribute.label", [""], 1));
removeAttribute.setAttribute("disabled", "true");
removeAttribute.setAttribute("label",
strings.formatStringFromName(
"inspector.menu.removeAttribute.label", [""], 1));
}
},
_resetNodeMenu: function () {
// Remove any extra items
while (this.lastNodemenuItem.nextSibling) {
let toDelete = this.lastNodemenuItem.nextSibling;
toDelete.parentNode.removeChild(toDelete);
}
}, },
/** /**
* Link menu items can be shown or hidden depending on the context and * Link menu items can be shown or hidden depending on the context and
* selected node, and their labels can vary. * selected node, and their labels can vary.
*
* @return {Array} list of visible menu items related to links.
*/ */
_setupNodeLinkMenu: function () { _getNodeLinkMenuItems: function () {
let linkSeparator = this.panelDoc.getElementById("node-menu-link-separator"); let linkFollow = new MenuItem({
let linkFollow = this.panelDoc.getElementById("node-menu-link-follow"); id: "node-menu-link-follow",
let linkCopy = this.panelDoc.getElementById("node-menu-link-copy"); visible: false,
click: () => this.onFollowLink(),
// Hide all by default. });
linkSeparator.setAttribute("hidden", "true"); let linkCopy = new MenuItem({
linkFollow.setAttribute("hidden", "true"); id: "node-menu-link-copy",
linkCopy.setAttribute("hidden", "true"); visible: false,
click: () => this.onCopyLink(),
});
// Get information about the right-clicked node. // Get information about the right-clicked node.
let popupNode = this.panelDoc.popupNode; let popupNode = this.contextMenuTarget;
if (!popupNode || !popupNode.classList.contains("link")) { if (!popupNode || !popupNode.classList.contains("link")) {
return; return [linkFollow, linkCopy];
} }
let type = popupNode.dataset.type; let type = popupNode.dataset.type;
if (type === "uri" || type === "cssresource" || type === "jsresource") { if (this._supportsResolveRelativeURL &&
// First make sure the target can resolve relative URLs. (type === "uri" || type === "cssresource" || type === "jsresource")) {
this.target.actorHasMethod("inspector", "resolveRelativeURL").then(canResolve => { // Links can't be opened in new tabs in the browser toolbox.
if (!canResolve) { if (type === "uri" && !this.target.chrome) {
return; linkFollow.visible = true;
} linkFollow.label = strings.GetStringFromName(
"inspector.menu.openUrlInNewTab.label");
} else if (type === "cssresource") {
linkFollow.visible = true;
linkFollow.label = toolboxStrings.GetStringFromName(
"toolbox.viewCssSourceInStyleEditor.label");
} else if (type === "jsresource") {
linkFollow.visible = true;
linkFollow.label = toolboxStrings.GetStringFromName(
"toolbox.viewJsSourceInDebugger.label");
}
linkSeparator.removeAttribute("hidden"); linkCopy.visible = true;
linkCopy.label = strings.GetStringFromName(
// Links can't be opened in new tabs in the browser toolbox. "inspector.menu.copyUrlToClipboard.label");
if (type === "uri" && !this.target.chrome) {
linkFollow.removeAttribute("hidden");
linkFollow.setAttribute("label", strings.GetStringFromName(
"inspector.menu.openUrlInNewTab.label"));
} else if (type === "cssresource") {
linkFollow.removeAttribute("hidden");
linkFollow.setAttribute("label", toolboxStrings.GetStringFromName(
"toolbox.viewCssSourceInStyleEditor.label"));
} else if (type === "jsresource") {
linkFollow.removeAttribute("hidden");
linkFollow.setAttribute("label", toolboxStrings.GetStringFromName(
"toolbox.viewJsSourceInDebugger.label"));
}
linkCopy.removeAttribute("hidden");
linkCopy.setAttribute("label", strings.GetStringFromName(
"inspector.menu.copyUrlToClipboard.label"));
}, console.error);
} else if (type === "idref") { } else if (type === "idref") {
linkSeparator.removeAttribute("hidden"); linkFollow.visible = true;
linkFollow.removeAttribute("hidden"); linkFollow.label = strings.formatStringFromName(
linkFollow.setAttribute("label", strings.formatStringFromName( "inspector.menu.selectElement.label", [popupNode.dataset.link], 1);
"inspector.menu.selectElement.label", [popupNode.dataset.link], 1));
} }
return [linkFollow, linkCopy];
}, },
_initMarkup: function () { _initMarkup: function () {
@ -1004,7 +1087,7 @@ InspectorPanel.prototype = {
this._markupFrame = doc.createElement("iframe"); this._markupFrame = doc.createElement("iframe");
this._markupFrame.setAttribute("flex", "1"); this._markupFrame.setAttribute("flex", "1");
this._markupFrame.setAttribute("tooltip", "aHTMLTooltip"); this._markupFrame.setAttribute("tooltip", "aHTMLTooltip");
this._markupFrame.setAttribute("context", "inspector-node-popup"); this._markupFrame.addEventListener("contextmenu", this._onContextMenu, true);
// This is needed to enable tooltips inside the iframe document. // This is needed to enable tooltips inside the iframe document.
this._markupFrame.addEventListener("load", this._onMarkupFrameLoad, true); this._markupFrame.addEventListener("load", this._onMarkupFrameLoad, true);
@ -1033,6 +1116,7 @@ InspectorPanel.prototype = {
if (this._markupFrame) { if (this._markupFrame) {
this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true); this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
this._markupFrame.removeEventListener("contextmenu", this._onContextMenu, true);
} }
if (this.markup) { if (this.markup) {
@ -1415,8 +1499,8 @@ InspectorPanel.prototype = {
* in the inspector contextual-menu. * in the inspector contextual-menu.
*/ */
onFollowLink: function () { onFollowLink: function () {
let type = this.panelDoc.popupNode.dataset.type; let type = this.contextMenuTarget.dataset.type;
let link = this.panelDoc.popupNode.dataset.link; let link = this.contextMenuTarget.dataset.link;
this.followAttributeLink(type, link); this.followAttributeLink(type, link);
}, },
@ -1433,7 +1517,7 @@ InspectorPanel.prototype = {
if (type === "uri" || type === "cssresource" || type === "jsresource") { if (type === "uri" || type === "cssresource" || type === "jsresource") {
// Open link in a new tab. // Open link in a new tab.
// When the inspector menu was setup on click (see _setupNodeLinkMenu), we // When the inspector menu was setup on click (see _getNodeLinkMenuItems), we
// already checked that resolveRelativeURL existed. // already checked that resolveRelativeURL existed.
this.inspector.resolveRelativeURL( this.inspector.resolveRelativeURL(
link, this.selection.nodeFront).then(url => { link, this.selection.nodeFront).then(url => {
@ -1466,7 +1550,7 @@ InspectorPanel.prototype = {
* in the inspector contextual-menu. * in the inspector contextual-menu.
*/ */
onCopyLink: function () { onCopyLink: function () {
let link = this.panelDoc.popupNode.dataset.link; let link = this.contextMenuTarget.dataset.link;
this.copyAttributeLink(link); this.copyAttributeLink(link);
}, },
@ -1475,7 +1559,7 @@ InspectorPanel.prototype = {
* This method is here for the benefit of copying links. * This method is here for the benefit of copying links.
*/ */
copyAttributeLink: function (link) { copyAttributeLink: function (link) {
// When the inspector menu was setup on click (see _setupNodeLinkMenu), we // When the inspector menu was setup on click (see _getNodeLinkMenuItems), we
// already checked that resolveRelativeURL existed. // already checked that resolveRelativeURL existed.
this.inspector.resolveRelativeURL(link, this.selection.nodeFront).then(url => { this.inspector.resolveRelativeURL(link, this.selection.nodeFront).then(url => {
clipboardHelper.copyString(url); clipboardHelper.copyString(url);

Просмотреть файл

@ -24,129 +24,6 @@
<script type="application/javascript;version=1.8" <script type="application/javascript;version=1.8"
src="chrome://devtools/content/shared/theme-switching.js"/> src="chrome://devtools/content/shared/theme-switching.js"/>
<popupset id="inspectorPopupSet">
<!-- Used by the Markup Panel and the Highlighter -->
<menupopup id="inspector-node-popup">
<menuitem id="node-menu-edithtml"
label="&inspectorHTMLEdit.label;"
accesskey="&inspectorHTMLEdit.accesskey;"
oncommand="inspector.editHTML()"/>
<menuitem id="node-menu-add"
label="&inspectorAddNode.label;"
accesskey="&inspectorAddNode.accesskey;"
oncommand="inspector.addNode()"/>
<menuitem id="node-menu-duplicatenode"
label="&inspectorDuplicateNode.label;"
oncommand="inspector.duplicateNode()"/>
<menuitem id="node-menu-delete"
label="&inspectorHTMLDelete.label;"
accesskey="&inspectorHTMLDelete.accesskey;"
oncommand="inspector.deleteNode()"/>
<menu label="&inspectorAttributesSubmenu.label;"
accesskey="&inspectorAttributesSubmenu.accesskey;">
<menupopup>
<menuitem id="node-menu-add-attribute"
label="&inspectorAddAttribute.label;"
accesskey="&inspectorAddAttribute.accesskey;"
oncommand="inspector.onAddAttribute()"/>
<menuitem id="node-menu-edit-attribute"
label="&inspectorEditAttribute.label;"
accesskey="&inspectorEditAttribute.accesskey;"
oncommand="inspector.onEditAttribute()"/>
<menuitem id="node-menu-remove-attribute"
label="&inspectorRemoveAttribute.label;"
accesskey="&inspectorRemoveAttribute.accesskey;"
oncommand="inspector.onRemoveAttribute()"/>
</menupopup>
</menu>
<menuseparator/>
<menuitem id="node-menu-pseudo-hover"
label=":hover" type="checkbox"
oncommand="inspector.togglePseudoClass(':hover')"/>
<menuitem id="node-menu-pseudo-active"
label=":active" type="checkbox"
oncommand="inspector.togglePseudoClass(':active')"/>
<menuitem id="node-menu-pseudo-focus"
label=":focus" type="checkbox"
oncommand="inspector.togglePseudoClass(':focus')"/>
<menuseparator/>
<menu label="&inspectorCopyHTMLSubmenu.label;">
<menupopup>
<menuitem id="node-menu-copyinner"
label="&inspectorCopyInnerHTML.label;"
accesskey="&inspectorCopyInnerHTML.accesskey;"
oncommand="inspector.copyInnerHTML()"/>
<menuitem id="node-menu-copyouter"
label="&inspectorCopyOuterHTML.label;"
accesskey="&inspectorCopyOuterHTML.accesskey;"
oncommand="inspector.copyOuterHTML()"/>
<menuitem id="node-menu-copyuniqueselector"
label="&inspectorCopyCSSSelector.label;"
accesskey="&inspectorCopyCSSSelector.accesskey;"
oncommand="inspector.copyUniqueSelector()"/>
<menuitem id="node-menu-copyimagedatauri"
label="&inspectorImageDataUri.label;"
oncommand="inspector.copyImageDataUri()"/>
</menupopup>
</menu>
<menu label="&inspectorPasteHTMLSubmenu.label;">
<menupopup>
<menuitem id="node-menu-pasteinnerhtml"
label="&inspectorPasteInnerHTML.label;"
accesskey="&inspectorPasteInnerHTML.accesskey;"
oncommand="inspector.pasteInnerHTML()"/>
<menuitem id="node-menu-pasteouterhtml"
label="&inspectorPasteOuterHTML.label;"
accesskey="&inspectorPasteOuterHTML.accesskey;"
oncommand="inspector.pasteOuterHTML()"/>
<menuitem id="node-menu-pastebefore"
label="&inspectorHTMLPasteBefore.label;"
accesskey="&inspectorHTMLPasteBefore.accesskey;"
oncommand="inspector.pasteAdjacentHTML('beforeBegin')"/>
<menuitem id="node-menu-pasteafter"
label="&inspectorHTMLPasteAfter.label;"
accesskey="&inspectorHTMLPasteAfter.accesskey;"
oncommand="inspector.pasteAdjacentHTML('afterEnd')"/>
<menuitem id="node-menu-pastefirstchild"
label="&inspectorHTMLPasteFirstChild.label;"
accesskey="&inspectorHTMLPasteFirstChild.accesskey;"
oncommand="inspector.pasteAdjacentHTML('afterBegin')"/>
<menuitem id="node-menu-pastelastchild"
label="&inspectorHTMLPasteLastChild.label;"
accesskey="&inspectorHTMLPasteLastChild.accesskey;"
oncommand="inspector.pasteAdjacentHTML('beforeEnd')"/>
</menupopup>
</menu>
<menuseparator/>
<menuitem id="node-menu-expand"
label="&inspectorExpandNode.label;"
oncommand="inspector.expandNode()"/>
<menuitem id="node-menu-collapse"
label="&inspectorCollapseNode.label;"
oncommand="inspector.collapseNode()"/>
<menuseparator/>
<menuitem id="node-menu-scrollnodeintoview"
label="&inspectorScrollNodeIntoView.label;"
accesskey="&inspectorScrollNodeIntoView.accesskey;"
oncommand="inspector.scrollNodeIntoView()"/>
<menuitem id="node-menu-screenshotnode"
label="&inspectorScreenshotNode.label;"
oncommand="inspector.screenshotNode()" />
<menuitem id="node-menu-useinconsole"
label="&inspectorUseInConsole.label;"
oncommand="inspector.useInConsole()"/>
<menuitem id="node-menu-showdomproperties"
label="&inspectorShowDOMProperties.label;"
oncommand="inspector.showDOMProperties()"/>
<menuseparator id="node-menu-link-separator"/>
<menuitem id="node-menu-link-follow"
oncommand="inspector.onFollowLink()"/>
<menuitem id="node-menu-link-copy"
oncommand="inspector.onCopyLink()"/>
</menupopup>
</popupset>
<box flex="1" class="devtools-responsive-container theme-body"> <box flex="1" class="devtools-responsive-container theme-body">
<vbox flex="1" class="devtools-main-content"> <vbox flex="1" class="devtools-main-content">
<html:div id="inspector-toolbar" <html:div id="inspector-toolbar"

Просмотреть файл

@ -10,7 +10,7 @@ const {Cc, Ci} = require("chrome");
const {Task} = require("devtools/shared/task"); const {Task} = require("devtools/shared/task");
const {InplaceEditor, editableItem} = const {InplaceEditor, editableItem} =
require("devtools/client/shared/inplace-editor"); require("devtools/client/shared/inplace-editor");
const {ReflowFront} = require("devtools/server/actors/layout"); const {ReflowFront} = require("devtools/shared/fronts/layout");
const {LocalizationHelper} = require("devtools/client/shared/l10n"); const {LocalizationHelper} = require("devtools/client/shared/l10n");
const {getCssProperties} = require("devtools/shared/fronts/css-properties"); const {getCssProperties} = require("devtools/shared/fronts/css-properties");

Просмотреть файл

@ -32,39 +32,19 @@ add_task(function* () {
}); });
function* assertCopyImageDataNotAvailable(inspector) { function* assertCopyImageDataNotAvailable(inspector) {
let menu = yield openNodeMenu(inspector); let allMenuItems = openContextMenuAndGetAllItems(inspector);
let item = allMenuItems.find(i => i.id === "node-menu-copyimagedatauri");
let item = menu.getElementsByAttribute("id", "node-menu-copyimagedatauri")[0];
ok(item, "The menu item was found in the contextual menu"); ok(item, "The menu item was found in the contextual menu");
is(item.getAttribute("disabled"), "true", "The menu item is disabled"); ok(item.disabled, "The menu item is disabled");
yield closeNodeMenu(inspector);
} }
function* assertCopyImageDataAvailable(inspector) { function* assertCopyImageDataAvailable(inspector) {
let menu = yield openNodeMenu(inspector); let allMenuItems = openContextMenuAndGetAllItems(inspector);
let item = allMenuItems.find(i => i.id === "node-menu-copyimagedatauri");
let item = menu.getElementsByAttribute("id", "node-menu-copyimagedatauri")[0];
ok(item, "The menu item was found in the contextual menu"); ok(item, "The menu item was found in the contextual menu");
is(item.getAttribute("disabled"), "", "The menu item is enabled"); ok(!item.disabled, "The menu item is enabled");
yield closeNodeMenu(inspector);
}
function* openNodeMenu(inspector) {
let onShown = once(inspector.nodemenu, "popupshown", false);
inspector.nodemenu.hidden = false;
inspector.nodemenu.openPopup();
yield onShown;
return inspector.nodemenu;
}
function* closeNodeMenu(inspector) {
let onHidden = once(inspector.nodemenu, "popuphidden", false);
inspector.nodemenu.hidden = true;
inspector.nodemenu.hidePopup();
yield onHidden;
return inspector.nodemenu;
} }
function triggerCopyImageUrlAndWaitForClipboard(expected, inspector) { function triggerCopyImageUrlAndWaitForClipboard(expected, inspector) {

Просмотреть файл

@ -77,9 +77,6 @@ const TEST_DATA = [{
add_task(function* () { add_task(function* () {
let {inspector} = yield openInspectorForURL(TEST_URL); let {inspector} = yield openInspectorForURL(TEST_URL);
let linkFollow = inspector.panelDoc.getElementById("node-menu-link-follow");
let linkCopy = inspector.panelDoc.getElementById("node-menu-link-copy");
for (let test of TEST_DATA) { for (let test of TEST_DATA) {
info("Selecting test node " + test.selector); info("Selecting test node " + test.selector);
yield selectNode(test.selector, inspector); yield selectNode(test.selector, inspector);
@ -91,7 +88,12 @@ add_task(function* () {
ok(popupNode, "Found the popupNode in attribute " + test.attributeName); ok(popupNode, "Found the popupNode in attribute " + test.attributeName);
info("Simulating a context click on the popupNode"); info("Simulating a context click on the popupNode");
contextMenuClick(popupNode); let allMenuItems = openContextMenuAndGetAllItems(inspector, {
target: popupNode,
});
let linkFollow = allMenuItems.find(i => i.id === "node-menu-link-follow");
let linkCopy = allMenuItems.find(i => i.id === "node-menu-link-copy");
// The contextual menu setup is async, because it needs to know if the // The contextual menu setup is async, because it needs to know if the
// inspector has the resolveRelativeURL method first. So call actorHasMethod // inspector has the resolveRelativeURL method first. So call actorHasMethod
@ -99,17 +101,17 @@ add_task(function* () {
// properly setup. // properly setup.
yield inspector.target.actorHasMethod("inspector", "resolveRelativeURL"); yield inspector.target.actorHasMethod("inspector", "resolveRelativeURL");
is(linkFollow.hasAttribute("hidden"), !test.isLinkFollowItemVisible, is(linkFollow.visible, test.isLinkFollowItemVisible,
"The follow-link item display is correct"); "The follow-link item display is correct");
is(linkCopy.hasAttribute("hidden"), !test.isLinkCopyItemVisible, is(linkCopy.visible, test.isLinkCopyItemVisible,
"The copy-link item display is correct"); "The copy-link item display is correct");
if (test.isLinkFollowItemVisible) { if (test.isLinkFollowItemVisible) {
is(linkFollow.getAttribute("label"), test.linkFollowItemLabel, is(linkFollow.label, test.linkFollowItemLabel,
"the follow-link label is correct"); "the follow-link label is correct");
} }
if (test.isLinkCopyItemVisible) { if (test.isLinkCopyItemVisible) {
is(linkCopy.getAttribute("label"), test.linkCopyItemLabel, is(linkCopy.label, test.linkCopyItemLabel,
"the copy-link label is correct"); "the copy-link label is correct");
} }
} }

Просмотреть файл

@ -17,8 +17,9 @@ add_task(function* () {
info("Set the popupNode to the node that contains the uri"); info("Set the popupNode to the node that contains the uri");
let {editor} = yield getContainerForSelector("video", inspector); let {editor} = yield getContainerForSelector("video", inspector);
let popupNode = editor.attrElements.get("poster").querySelector(".link"); openContextMenuAndGetAllItems(inspector, {
inspector.panelDoc.popupNode = popupNode; target: editor.attrElements.get("poster").querySelector(".link"),
});
info("Follow the link and wait for the new tab to open"); info("Follow the link and wait for the new tab to open");
let onTabOpened = once(gBrowser.tabContainer, "TabOpen"); let onTabOpened = once(gBrowser.tabContainer, "TabOpen");
@ -36,8 +37,9 @@ add_task(function* () {
info("Set the popupNode to the node that contains the ref"); info("Set the popupNode to the node that contains the ref");
({editor} = yield getContainerForSelector("label", inspector)); ({editor} = yield getContainerForSelector("label", inspector));
popupNode = editor.attrElements.get("for").querySelector(".link"); openContextMenuAndGetAllItems(inspector, {
inspector.panelDoc.popupNode = popupNode; target: editor.attrElements.get("for").querySelector(".link"),
});
info("Follow the link and wait for the new node to be selected"); info("Follow the link and wait for the new node to be selected");
let onSelection = inspector.selection.once("new-node-front"); let onSelection = inspector.selection.once("new-node-front");
@ -52,8 +54,9 @@ add_task(function* () {
info("Set the popupNode to the node that contains the ref"); info("Set the popupNode to the node that contains the ref");
({editor} = yield getContainerForSelector("output", inspector)); ({editor} = yield getContainerForSelector("output", inspector));
popupNode = editor.attrElements.get("for").querySelectorAll(".link")[2]; openContextMenuAndGetAllItems(inspector, {
inspector.panelDoc.popupNode = popupNode; target: editor.attrElements.get("for").querySelectorAll(".link")[2],
});
info("Try to follow the link and check that no new node were selected"); info("Try to follow the link and check that no new node were selected");
let onFailed = inspector.once("idref-attribute-link-failed"); let onFailed = inspector.once("idref-attribute-link-failed");

Просмотреть файл

@ -17,8 +17,9 @@ add_task(function* () {
info("Set the popupNode to the node that contains the uri"); info("Set the popupNode to the node that contains the uri");
let {editor} = yield getContainerForSelector("link", inspector); let {editor} = yield getContainerForSelector("link", inspector);
let popupNode = editor.attrElements.get("href").querySelector(".link"); openContextMenuAndGetAllItems(inspector, {
inspector.panelDoc.popupNode = popupNode; target: editor.attrElements.get("href").querySelector(".link"),
});
info("Follow the link and wait for the style-editor to open"); info("Follow the link and wait for the style-editor to open");
let onStyleEditorReady = toolbox.once("styleeditor-ready"); let onStyleEditorReady = toolbox.once("styleeditor-ready");
@ -37,8 +38,9 @@ add_task(function* () {
info("Set the popupNode to the node that contains the uri"); info("Set the popupNode to the node that contains the uri");
({editor} = yield getContainerForSelector("script", inspector)); ({editor} = yield getContainerForSelector("script", inspector));
popupNode = editor.attrElements.get("src").querySelector(".link"); openContextMenuAndGetAllItems(inspector, {
inspector.panelDoc.popupNode = popupNode; target: editor.attrElements.get("src").querySelector(".link"),
});
info("Follow the link and wait for the debugger to open"); info("Follow the link and wait for the debugger to open");
let onDebuggerReady = toolbox.once("jsdebugger-ready"); let onDebuggerReady = toolbox.once("jsdebugger-ready");

Просмотреть файл

@ -294,31 +294,25 @@ function wait(ms) {
*/ */
var isEditingMenuDisabled = Task.async( var isEditingMenuDisabled = Task.async(
function* (nodeFront, inspector, assert = true) { function* (nodeFront, inspector, assert = true) {
let doc = inspector.panelDoc;
let deleteMenuItem = doc.getElementById("node-menu-delete");
let editHTMLMenuItem = doc.getElementById("node-menu-edithtml");
let pasteHTMLMenuItem = doc.getElementById("node-menu-pasteouterhtml");
// To ensure clipboard contains something to paste. // To ensure clipboard contains something to paste.
clipboard.set("<p>test</p>", "html"); clipboard.set("<p>test</p>", "html");
let menu = inspector.nodemenu;
yield selectNode(nodeFront, inspector); yield selectNode(nodeFront, inspector);
yield reopenMenu(menu); let allMenuItems = openContextMenuAndGetAllItems(inspector);
let isDeleteMenuDisabled = deleteMenuItem.hasAttribute("disabled"); let deleteMenuItem = allMenuItems.find(i => i.id === "node-menu-delete");
let isEditHTMLMenuDisabled = editHTMLMenuItem.hasAttribute("disabled"); let editHTMLMenuItem = allMenuItems.find(i => i.id === "node-menu-edithtml");
let isPasteHTMLMenuDisabled = pasteHTMLMenuItem.hasAttribute("disabled"); let pasteHTMLMenuItem = allMenuItems.find(i => i.id === "node-menu-pasteouterhtml");
if (assert) { if (assert) {
ok(isDeleteMenuDisabled, "Delete menu item is disabled"); ok(deleteMenuItem.disabled, "Delete menu item is disabled");
ok(isEditHTMLMenuDisabled, "Edit HTML menu item is disabled"); ok(editHTMLMenuItem.disabled, "Edit HTML menu item is disabled");
ok(isPasteHTMLMenuDisabled, "Paste HTML menu item is disabled"); ok(pasteHTMLMenuItem.disabled, "Paste HTML menu item is disabled");
} }
return isDeleteMenuDisabled && return deleteMenuItem.disabled &&
isEditHTMLMenuDisabled && editHTMLMenuItem.disabled &&
isPasteHTMLMenuDisabled; pasteHTMLMenuItem.disabled;
}); });
/** /**
@ -332,50 +326,25 @@ function* (nodeFront, inspector, assert = true) {
*/ */
var isEditingMenuEnabled = Task.async( var isEditingMenuEnabled = Task.async(
function* (nodeFront, inspector, assert = true) { function* (nodeFront, inspector, assert = true) {
let doc = inspector.panelDoc;
let deleteMenuItem = doc.getElementById("node-menu-delete");
let editHTMLMenuItem = doc.getElementById("node-menu-edithtml");
let pasteHTMLMenuItem = doc.getElementById("node-menu-pasteouterhtml");
// To ensure clipboard contains something to paste. // To ensure clipboard contains something to paste.
clipboard.set("<p>test</p>", "html"); clipboard.set("<p>test</p>", "html");
let menu = inspector.nodemenu;
yield selectNode(nodeFront, inspector); yield selectNode(nodeFront, inspector);
yield reopenMenu(menu); let allMenuItems = openContextMenuAndGetAllItems(inspector);
let isDeleteMenuDisabled = deleteMenuItem.hasAttribute("disabled"); let deleteMenuItem = allMenuItems.find(i => i.id === "node-menu-delete");
let isEditHTMLMenuDisabled = editHTMLMenuItem.hasAttribute("disabled"); let editHTMLMenuItem = allMenuItems.find(i => i.id === "node-menu-edithtml");
let isPasteHTMLMenuDisabled = pasteHTMLMenuItem.hasAttribute("disabled"); let pasteHTMLMenuItem = allMenuItems.find(i => i.id === "node-menu-pasteouterhtml");
if (assert) { if (assert) {
ok(!isDeleteMenuDisabled, "Delete menu item is enabled"); ok(!deleteMenuItem.disabled, "Delete menu item is enabled");
ok(!isEditHTMLMenuDisabled, "Edit HTML menu item is enabled"); ok(!editHTMLMenuItem.disabled, "Edit HTML menu item is enabled");
ok(!isPasteHTMLMenuDisabled, "Paste HTML menu item is enabled"); ok(!pasteHTMLMenuItem.disabled, "Paste HTML menu item is enabled");
} }
return !isDeleteMenuDisabled && return !deleteMenuItem.disabled &&
!isEditHTMLMenuDisabled && !editHTMLMenuItem.disabled &&
!isPasteHTMLMenuDisabled; !pasteHTMLMenuItem.disabled;
});
/**
* Open a menu (closing it first if necessary).
* @param {DOMNode} menu A menu that implements hidePopup/openPopup
* @return a promise that resolves once the menu is opened.
*/
var reopenMenu = Task.async(function* (menu) {
// First close it is if it is already opened.
if (menu.state == "closing" || menu.state == "open") {
let popuphidden = once(menu, "popuphidden", true);
menu.hidePopup();
yield popuphidden;
}
// Then open it and return once
let popupshown = once(menu, "popupshown", true);
menu.openPopup();
yield popupshown;
}); });
/** /**
@ -490,20 +459,6 @@ function createTestHTTPServer() {
return server; return server;
} }
/**
* A helper that simulates a contextmenu event on the given chrome DOM element.
*/
function contextMenuClick(element) {
let evt = element.ownerDocument.createEvent("MouseEvents");
let buttonRight = 2;
evt.initMouseEvent("contextmenu", true, true,
element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false, false, false,
false, buttonRight, null);
element.dispatchEvent(evt);
}
/** /**
* Registers new backend tab actor. * Registers new backend tab actor.
* *

Просмотреть файл

@ -12,11 +12,11 @@ add_task(function* () {
let {inspector} = yield openInspectorForURL(TEST_URL); let {inspector} = yield openInspectorForURL(TEST_URL);
let {panelDoc} = inspector; let {panelDoc} = inspector;
let allMenuItems = openContextMenuAndGetAllItems(inspector);
let menuItem = allMenuItems.find(item => item.id === "node-menu-add");
ok(menuItem, "The item is in the menu");
let toolbarButton = let toolbarButton =
panelDoc.querySelector("#inspector-toolbar #inspector-element-add-button"); panelDoc.querySelector("#inspector-toolbar #inspector-element-add-button");
let menuItem =
panelDoc.querySelector("#inspector-node-popup #node-menu-add");
ok(toolbarButton, "The add button is in the toolbar"); ok(toolbarButton, "The add button is in the toolbar");
ok(menuItem, "The item is in the menu");
}); });

Просмотреть файл

@ -49,14 +49,15 @@ add_task(function* () {
function assertState(isEnabled, inspector, desc) { function assertState(isEnabled, inspector, desc) {
let doc = inspector.panelDoc; let doc = inspector.panelDoc;
let btn = doc.querySelector("#inspector-element-add-button"); let btn = doc.querySelector("#inspector-element-add-button");
let item = doc.querySelector("#node-menu-add");
// Force an update of the context menu to make sure menu items are updated // Force an update of the context menu to make sure menu items are updated
// according to the current selection. This normally happens when the menu is // according to the current selection. This normally happens when the menu is
// opened, but for the sake of this test's simplicity, we directly call the // opened, but for the sake of this test's simplicity, we directly call the
// private update function instead. // private update function instead.
inspector._setupNodeMenu({target: {}}); let allMenuItems = openContextMenuAndGetAllItems(inspector);
let menuItem = allMenuItems.find(item => item.id === "node-menu-add");
ok(menuItem, "The item is in the menu");
is(!menuItem.disabled, isEnabled, desc);
is(!btn.hasAttribute("disabled"), isEnabled, desc); is(!btn.hasAttribute("disabled"), isEnabled, desc);
is(!item.hasAttribute("disabled"), isEnabled, desc);
} }

Просмотреть файл

@ -28,16 +28,14 @@ add_task(function* () {
info("Getting the node container in the markup view."); info("Getting the node container in the markup view.");
let container = yield getContainerForSelector("#deleteManually", inspector); let container = yield getContainerForSelector("#deleteManually", inspector);
info("Simulating right-click on the markup view container."); let allMenuItems = openContextMenuAndGetAllItems(inspector, {
EventUtils.synthesizeMouse(container.tagLine, 2, 2, target: container.tagLine,
{type: "contextmenu", button: 2}, inspector.panelWin); });
let menuItem = allMenuItems.find(item => item.id === "node-menu-delete");
info("Waiting for the context menu to open.");
yield once(inspector.panelDoc.getElementById("inspectorPopupSet"),
"popupshown");
info("Clicking 'Delete Node' in the context menu."); info("Clicking 'Delete Node' in the context menu.");
inspector.panelDoc.getElementById("node-menu-delete").click(); is(menuItem.disabled, false, "delete menu item is enabled");
menuItem.click();
info("Waiting for inspector to update."); info("Waiting for inspector to update.");
yield inspector.once("inspector-updated"); yield inspector.once("inspector-updated");

Просмотреть файл

@ -15,11 +15,6 @@ add_task(function* () {
let {inspector} = yield openInspectorForURL(TEST_URL); let {inspector} = yield openInspectorForURL(TEST_URL);
let nodeMenuCollapseElement = inspector.panelDoc.getElementById(
"node-menu-collapse");
let nodeMenuExpandElement = inspector.panelDoc.getElementById(
"node-menu-expand");
info("Selecting the parent node"); info("Selecting the parent node");
let front = yield getNodeFrontForSelector("#parent-node", inspector); let front = yield getNodeFrontForSelector("#parent-node", inspector);
@ -27,31 +22,39 @@ add_task(function* () {
yield selectNode(front, inspector); yield selectNode(front, inspector);
info("Simulating context menu click on the selected node container."); info("Simulating context menu click on the selected node container.");
contextMenuClick(getContainerForNodeFront(front, inspector).tagLine); let allMenuItems = openContextMenuAndGetAllItems(inspector, {
target: getContainerForNodeFront(front, inspector).tagLine,
});
let nodeMenuCollapseElement =
allMenuItems.find(item => item.id === "node-menu-collapse");
let nodeMenuExpandElement =
allMenuItems.find(item => item.id === "node-menu-expand");
ok(nodeMenuCollapseElement.hasAttribute("disabled"), ok(nodeMenuCollapseElement.disabled, "Collapse option is disabled");
"Collapse option is disabled"); ok(!nodeMenuExpandElement.disabled, "ExpandAll option is enabled");
ok(!nodeMenuExpandElement.hasAttribute("disabled"),
"ExpandAll option is enabled");
info("Testing whether expansion works properly"); info("Testing whether expansion works properly");
dispatchCommandEvent(nodeMenuExpandElement); nodeMenuExpandElement.click();
info("Waiting for expansion to occur"); info("Waiting for expansion to occur");
yield waitForMultipleChildrenUpdates(inspector); yield waitForMultipleChildrenUpdates(inspector);
let markUpContainer = getContainerForNodeFront(front, inspector); let markUpContainer = getContainerForNodeFront(front, inspector);
ok(markUpContainer.expanded, "node has been successfully expanded"); ok(markUpContainer.expanded, "node has been successfully expanded");
// reslecting node after expansion // reselecting node after expansion
yield selectNode(front, inspector); yield selectNode(front, inspector);
info("Testing whether collapse works properly"); info("Testing whether collapse works properly");
info("Simulating context menu click on the selected node container."); info("Simulating context menu click on the selected node container.");
contextMenuClick(getContainerForNodeFront(front, inspector).tagLine); allMenuItems = openContextMenuAndGetAllItems(inspector, {
target: getContainerForNodeFront(front, inspector).tagLine,
});
nodeMenuCollapseElement =
allMenuItems.find(item => item.id === "node-menu-collapse");
ok(!nodeMenuCollapseElement.hasAttribute("disabled"), ok(!nodeMenuCollapseElement.disabled, "Collapse option is enabled");
"Collapse option is enabled"); nodeMenuCollapseElement.click();
dispatchCommandEvent(nodeMenuCollapseElement);
info("Waiting for collapse to occur"); info("Waiting for collapse to occur");
yield waitForMultipleChildrenUpdates(inspector); yield waitForMultipleChildrenUpdates(inspector);
ok(!markUpContainer.expanded, "node has been successfully collapsed"); ok(!markUpContainer.expanded, "node has been successfully collapsed");

Просмотреть файл

@ -234,15 +234,16 @@ add_task(function* () {
? nodeFrontContainer.tagLine.querySelector( ? nodeFrontContainer.tagLine.querySelector(
`[data-attr="${attributeTrigger}"]`) `[data-attr="${attributeTrigger}"]`)
: nodeFrontContainer.tagLine; : nodeFrontContainer.tagLine;
contextMenuClick(contextMenuTrigger);
for (let menuitem of ALL_MENU_ITEMS) { let allMenuItems = openContextMenuAndGetAllItems(inspector, {
let elt = inspector.panelDoc.getElementById(menuitem); target: contextMenuTrigger,
let shouldBeDisabled = disabled.indexOf(menuitem) !== -1; });
let isDisabled = elt.hasAttribute("disabled");
is(isDisabled, shouldBeDisabled, for (let id of ALL_MENU_ITEMS) {
`#${menuitem} should be ${shouldBeDisabled ? "disabled" : "enabled"} `); let menuItem = allMenuItems.find(item => item.id === id);
let shouldBeDisabled = disabled.indexOf(id) !== -1;
is(menuItem.disabled, shouldBeDisabled,
`#${id} should be ${shouldBeDisabled ? "disabled" : "enabled"} `);
} }
} }
}); });
@ -275,17 +276,3 @@ function setupClipboard(data, type) {
clipboard.set("", "text"); clipboard.set("", "text");
} }
} }
/**
* A helper that simulates a contextmenu event on the given chrome DOM element.
*/
function contextMenuClick(element) {
let evt = element.ownerDocument.createEvent("MouseEvents");
let button = 2;
evt.initMouseEvent("contextmenu", true, true,
element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
false, false, false, button, null);
element.dispatchEvent(evt);
}

Просмотреть файл

@ -40,9 +40,10 @@ add_task(function* () {
info("Testing " + desc); info("Testing " + desc);
yield selectNode(selector, inspector); yield selectNode(selector, inspector);
let item = inspector.panelDoc.getElementById(id); let allMenuItems = openContextMenuAndGetAllItems(inspector);
let item = allMenuItems.find(i => i.id === id);
ok(item, "The popup has a " + desc + " menu item."); ok(item, "The popup has a " + desc + " menu item.");
yield waitForClipboard(() => item.doCommand(), text); yield waitForClipboard(() => item.click(), text);
} }
}); });

Просмотреть файл

@ -49,11 +49,12 @@ add_task(function* () {
let nodeFront = yield getNodeFront(outerHTMLSelector, inspector); let nodeFront = yield getNodeFront(outerHTMLSelector, inspector);
yield selectNode(nodeFront, inspector); yield selectNode(nodeFront, inspector);
contextMenuClick(getContainerForNodeFront(nodeFront, inspector).tagLine); let allMenuItems = openContextMenuAndGetAllItems(inspector, {
target: getContainerForNodeFront(nodeFront, inspector).tagLine,
});
let onNodeReselected = inspector.markup.once("reselectedonremoved"); let onNodeReselected = inspector.markup.once("reselectedonremoved");
let menu = inspector.panelDoc.getElementById("node-menu-pasteouterhtml"); allMenuItems.find(item => item.id === "node-menu-pasteouterhtml").click();
dispatchCommandEvent(menu);
info("Waiting for inspector selection to update"); info("Waiting for inspector selection to update");
yield onNodeReselected; yield onNodeReselected;
@ -76,12 +77,12 @@ add_task(function* () {
let nodeFront = yield getNodeFront(innerHTMLSelector, inspector); let nodeFront = yield getNodeFront(innerHTMLSelector, inspector);
yield selectNode(nodeFront, inspector); yield selectNode(nodeFront, inspector);
contextMenuClick(getContainerForNodeFront(nodeFront, inspector).tagLine); let allMenuItems = openContextMenuAndGetAllItems(inspector, {
target: getContainerForNodeFront(nodeFront, inspector).tagLine,
});
let onMutation = inspector.once("markupmutation"); let onMutation = inspector.once("markupmutation");
let menu = inspector.panelDoc.getElementById("node-menu-pasteinnerhtml"); allMenuItems.find(item => item.id === "node-menu-pasteinnerhtml").click();
dispatchCommandEvent(menu);
info("Waiting for mutation to occur"); info("Waiting for mutation to occur");
yield onMutation; yield onMutation;
@ -102,14 +103,14 @@ add_task(function* () {
let markupTagLine = getContainerForNodeFront(nodeFront, inspector).tagLine; let markupTagLine = getContainerForNodeFront(nodeFront, inspector).tagLine;
for (let { clipboardData, menuId } of PASTE_ADJACENT_HTML_DATA) { for (let { clipboardData, menuId } of PASTE_ADJACENT_HTML_DATA) {
let menu = inspector.panelDoc.getElementById(menuId); let allMenuItems = openContextMenuAndGetAllItems(inspector, {
info(`Testing ${getLabelFor(menu)} for ${clipboardData}`); target: markupTagLine,
});
info(`Testing ${menuId} for ${clipboardData}`);
clipboard.set(clipboardData); clipboard.set(clipboardData);
contextMenuClick(markupTagLine);
let onMutation = inspector.once("markupmutation"); let onMutation = inspector.once("markupmutation");
dispatchCommandEvent(menu); allMenuItems.find(item => item.id === menuId).click();
info("Waiting for mutation to occur"); info("Waiting for mutation to occur");
yield onMutation; yield onMutation;
} }
@ -124,32 +125,4 @@ add_task(function* () {
ok(html.trim() === "1<span class=\"ref\">234</span>", ok(html.trim() === "1<span class=\"ref\">234</span>",
"Undo works for paste adjacent HTML"); "Undo works for paste adjacent HTML");
} }
function dispatchCommandEvent(node) {
info("Dispatching command event on " + node);
let commandEvent = document.createEvent("XULCommandEvent");
commandEvent.initCommandEvent("command", true, true, window, 0, false,
false, false, false, null);
node.dispatchEvent(commandEvent);
}
function contextMenuClick(element) {
info("Simulating contextmenu event on " + element);
let evt = element.ownerDocument.createEvent("MouseEvents");
let button = 2;
evt.initMouseEvent("contextmenu", true, true,
element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
false, false, false, button, null);
element.dispatchEvent(evt);
}
function getLabelFor(elt) {
if (typeof elt === "string") {
elt = inspector.panelDoc.querySelector(elt);
}
let isInPasteSubMenu = elt.matches("#node-menu-paste-extra-submenu *");
return `"${isInPasteSubMenu ? "Paste > " : ""}${elt.label}"`;
}
}); });

Просмотреть файл

@ -18,11 +18,15 @@ add_task(function* () {
function* testUseInConsole() { function* testUseInConsole() {
info("Testing 'Use in Console' menu item."); info("Testing 'Use in Console' menu item.");
let useInConsoleNode = inspector.panelDoc.getElementById(
"node-menu-useinconsole");
yield selectNode("#console-var", inspector); yield selectNode("#console-var", inspector);
dispatchCommandEvent(useInConsoleNode); let container = yield getContainerForSelector("#console-var", inspector);
let allMenuItems = openContextMenuAndGetAllItems(inspector, {
target: container.tagLine,
});
let menuItem = allMenuItems.find(i => i.id === "node-menu-useinconsole");
menuItem.click();
yield inspector.once("console-var-ready"); yield inspector.once("console-var-ready");
let hud = toolbox.getPanel("webconsole").hud; let hud = toolbox.getPanel("webconsole").hud;
@ -36,7 +40,7 @@ add_task(function* () {
"variable temp0 references correct node"); "variable temp0 references correct node");
yield selectNode("#console-var-multi", inspector); yield selectNode("#console-var-multi", inspector);
dispatchCommandEvent(useInConsoleNode); menuItem.click();
yield inspector.once("console-var-ready"); yield inspector.once("console-var-ready");
is(jstermInput.value, "temp1", "second console variable is named temp1"); is(jstermInput.value, "temp1", "second console variable is named temp1");

Просмотреть файл

@ -16,11 +16,10 @@ add_task(function* () {
yield testRemoveAttribute(); yield testRemoveAttribute();
function* testAddAttribute() { function* testAddAttribute() {
info("Testing 'Add Attribute' menu item");
let addAttribute = getMenuItem("node-menu-add-attribute");
info("Triggering 'Add Attribute' and waiting for mutation to occur"); info("Triggering 'Add Attribute' and waiting for mutation to occur");
dispatchCommandEvent(addAttribute); let addAttribute = getMenuItem("node-menu-add-attribute");
addAttribute.click();
EventUtils.synthesizeKey('class="u-hidden"', {}); EventUtils.synthesizeKey('class="u-hidden"', {});
let onMutation = inspector.once("markupmutation"); let onMutation = inspector.once("markupmutation");
EventUtils.synthesizeKey("VK_RETURN", {}); EventUtils.synthesizeKey("VK_RETURN", {});
@ -39,7 +38,7 @@ add_task(function* () {
type: "attribute", type: "attribute",
name: "data-edit" name: "data-edit"
}; };
dispatchCommandEvent(editAttribute); editAttribute.click();
EventUtils.synthesizeKey("data-edit='edited'", {}); EventUtils.synthesizeKey("data-edit='edited'", {});
let onMutation = inspector.once("markupmutation"); let onMutation = inspector.once("markupmutation");
EventUtils.synthesizeKey("VK_RETURN", {}); EventUtils.synthesizeKey("VK_RETURN", {});
@ -60,7 +59,7 @@ add_task(function* () {
name: "data-remove" name: "data-remove"
}; };
let onMutation = inspector.once("markupmutation"); let onMutation = inspector.once("markupmutation");
dispatchCommandEvent(removeAttribute); removeAttribute.click();
yield onMutation; yield onMutation;
let hasAttribute = yield testActor.hasNode("#attributes[data-remove]"); let hasAttribute = yield testActor.hasNode("#attributes[data-remove]");
@ -68,8 +67,13 @@ add_task(function* () {
} }
function getMenuItem(id) { function getMenuItem(id) {
let attribute = inspector.panelDoc.getElementById(id); let allMenuItems = openContextMenuAndGetAllItems(inspector, {
ok(attribute, "Menu item '" + id + "' found"); target: getContainerForSelector("#attributes", inspector).tagLine,
return attribute; });
let menuItem = allMenuItems.find(i => i.id === id);
ok(menuItem, "Menu item '" + id + "' found");
// Close the menu so synthesizing future keys won't select menu items.
EventUtils.synthesizeKey("VK_ESCAPE", {});
return menuItem;
} }
}); });

Просмотреть файл

@ -14,14 +14,15 @@ add_task(function* () {
yield testScrollIntoView(); yield testScrollIntoView();
function* testShowDOMProperties() { function* testShowDOMProperties() {
info("Testing 'Show DOM Properties' menu item."); info("Testing 'Show DOM Properties' menu item.");
let showDOMPropertiesNode = inspector.panelDoc.getElementById( let allMenuItems = openContextMenuAndGetAllItems(inspector);
"node-menu-showdomproperties"); let showDOMPropertiesNode =
allMenuItems.find(item => item.id === "node-menu-showdomproperties");
ok(showDOMPropertiesNode, "the popup menu has a show dom properties item"); ok(showDOMPropertiesNode, "the popup menu has a show dom properties item");
let consoleOpened = toolbox.once("webconsole-ready"); let consoleOpened = toolbox.once("webconsole-ready");
info("Triggering 'Show DOM Properties' and waiting for inspector open"); info("Triggering 'Show DOM Properties' and waiting for inspector open");
dispatchCommandEvent(showDOMPropertiesNode); showDOMPropertiesNode.click();
yield consoleOpened; yield consoleOpened;
let webconsoleUI = toolbox.getPanel("webconsole").hud.ui; let webconsoleUI = toolbox.getPanel("webconsole").hud.ui;
@ -38,12 +39,14 @@ add_task(function* () {
is((yield testActor.getNumberOfElementMatches(".duplicate")), 1, is((yield testActor.getNumberOfElementMatches(".duplicate")), 1,
"There should initially be 1 .duplicate node"); "There should initially be 1 .duplicate node");
let menuItem = inspector.panelDoc.getElementById("node-menu-duplicatenode"); let allMenuItems = openContextMenuAndGetAllItems(inspector);
let menuItem =
allMenuItems.find(item => item.id === "node-menu-duplicatenode");
ok(menuItem, "'Duplicate node' menu item should exist"); ok(menuItem, "'Duplicate node' menu item should exist");
info("Triggering 'Duplicate Node' and waiting for inspector to update"); info("Triggering 'Duplicate Node' and waiting for inspector to update");
let updated = inspector.once("markupmutation"); let updated = inspector.once("markupmutation");
dispatchCommandEvent(menuItem); menuItem.click();
yield updated; yield updated;
is((yield testActor.getNumberOfElementMatches(".duplicate")), 2, is((yield testActor.getNumberOfElementMatches(".duplicate")), 2,
@ -57,12 +60,13 @@ add_task(function* () {
function* testDeleteNode() { function* testDeleteNode() {
info("Testing 'Delete Node' menu item for normal elements."); info("Testing 'Delete Node' menu item for normal elements.");
yield selectNode("#delete", inspector); yield selectNode("#delete", inspector);
let deleteNode = inspector.panelDoc.getElementById("node-menu-delete"); let allMenuItems = openContextMenuAndGetAllItems(inspector);
let deleteNode = allMenuItems.find(item => item.id === "node-menu-delete");
ok(deleteNode, "the popup menu has a delete menu item"); ok(deleteNode, "the popup menu has a delete menu item");
let updated = inspector.once("inspector-updated"); let updated = inspector.once("inspector-updated");
info("Triggering 'Delete Node' and waiting for inspector to update"); info("Triggering 'Delete Node' and waiting for inspector to update");
dispatchCommandEvent(deleteNode); deleteNode.click();
yield updated; yield updated;
ok(!(yield testActor.hasNode("#delete")), "Node deleted"); ok(!(yield testActor.hasNode("#delete")), "Node deleted");
@ -72,8 +76,9 @@ add_task(function* () {
info("Testing 'Delete Node' menu item does not delete root node."); info("Testing 'Delete Node' menu item does not delete root node.");
yield selectNode("html", inspector); yield selectNode("html", inspector);
let deleteNode = inspector.panelDoc.getElementById("node-menu-delete"); let allMenuItems = openContextMenuAndGetAllItems(inspector);
dispatchCommandEvent(deleteNode); let deleteNode = allMenuItems.find(item => item.id === "node-menu-delete");
deleteNode.click();
let deferred = promise.defer(); let deferred = promise.defer();
executeSoon(deferred.resolve); executeSoon(deferred.resolve);

Просмотреть файл

@ -15,26 +15,17 @@ add_task(function* () {
let {inspector, testActor} = yield openInspectorForURL(TEST_URI); let {inspector, testActor} = yield openInspectorForURL(TEST_URI);
yield selectNode("div", inspector); yield selectNode("div", inspector);
info("Getting the inspector ctx menu and opening it"); let allMenuItems = openContextMenuAndGetAllItems(inspector);
let menu = inspector.panelDoc.getElementById("inspector-node-popup");
yield openMenu(menu);
yield testMenuItems(testActor, menu, inspector); yield testMenuItems(testActor, allMenuItems, inspector);
menu.hidePopup();
}); });
function openMenu(menu) { function* testMenuItems(testActor, allMenuItems, inspector) {
let promise = once(menu, "popupshowing", true);
menu.openPopup();
return promise;
}
function* testMenuItems(testActor, menu, inspector) {
for (let pseudo of PSEUDOS) { for (let pseudo of PSEUDOS) {
let menuitem = inspector.panelDoc.getElementById( let menuItem =
"node-menu-pseudo-" + pseudo); allMenuItems.find(item => item.id === "node-menu-pseudo-" + pseudo);
ok(menuitem, ":" + pseudo + " menuitem exists"); ok(menuItem, ":" + pseudo + " menuitem exists");
is(menuItem.disabled, false, ":" + pseudo + " menuitem is enabled");
// Give the inspector panels a chance to update when the pseudoclass changes // Give the inspector panels a chance to update when the pseudoclass changes
let onPseudo = inspector.selection.once("pseudoclass"); let onPseudo = inspector.selection.once("pseudoclass");
@ -43,7 +34,7 @@ function* testMenuItems(testActor, menu, inspector) {
// Walker uses SDK-events so calling walker.once does not return a promise. // Walker uses SDK-events so calling walker.once does not return a promise.
let onMutations = once(inspector.walker, "mutations"); let onMutations = once(inspector.walker, "mutations");
menuitem.doCommand(); menuItem.click();
yield onPseudo; yield onPseudo;
yield onRefresh; yield onRefresh;

Просмотреть файл

@ -194,11 +194,14 @@ var openInspector = Task.async(function* (hostType) {
hostType); hostType);
let inspector = toolbox.getPanel("inspector"); let inspector = toolbox.getPanel("inspector");
info("Waiting for the inspector to update");
if (inspector._updateProgress) { if (inspector._updateProgress) {
info("Need to wait for the inspector to update");
yield inspector.once("inspector-updated"); yield inspector.once("inspector-updated");
} }
info("Waiting for actor features to be detected");
yield inspector._detectingActorFeatures;
yield registerTestActor(toolbox.target.client); yield registerTestActor(toolbox.target.client);
let testActor = yield getTestActor(toolbox); let testActor = yield getTestActor(toolbox);
@ -512,32 +515,6 @@ function redoChange(inspector) {
return mutated; return mutated;
} }
/**
* Dispatch a command event on a node (e.g. click on a contextual menu item).
* @param {DOMNode} node
*/
function dispatchCommandEvent(node) {
info("Dispatching command event on " + node);
let commandEvent = document.createEvent("XULCommandEvent");
commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
false, false, null);
node.dispatchEvent(commandEvent);
}
/**
* A helper that simulates a contextmenu event on the given chrome DOM element.
*/
function contextMenuClick(element) {
let evt = element.ownerDocument.createEvent("MouseEvents");
let button = 2;
evt.initMouseEvent("contextmenu", true, true,
element.ownerDocument.defaultView, 1, 0, 0, 0, 0, false,
false, false, false, button, null);
element.dispatchEvent(evt);
}
/** /**
* A helper that fetches a front for a node that matches the given selector or * A helper that fetches a front for a node that matches the given selector or
* doctype node if the selector is falsy. * doctype node if the selector is falsy.
@ -829,3 +806,23 @@ function assertHoverTooltipOn(tooltip, element) {
ok(false, "No tooltip is defined on hover of the given element"); ok(false, "No tooltip is defined on hover of the given element");
}); });
} }
/**
* Open the inspector menu and return all of it's items in a flat array
* @param {InspectorPanel} inspector
* @param {Object} options to pass into openMenu
* @return An array of MenuItems
*/
function openContextMenuAndGetAllItems(inspector, options) {
let menu = inspector._openMenu(options);
// Flatten all menu items into a single array to make searching through it easier
let allItems = [].concat.apply([], menu.items.map(function addItem(item) {
if (item.submenu) {
return addItem(item.submenu.items);
}
return item;
}));
return allItems;
}

Просмотреть файл

@ -1,106 +1,3 @@
<!-- LOCALIZATION NOTE (inspectorHTMLEdit.label): This is the label shown
in the inspector contextual-menu for the item that lets users edit the
(outer) HTML of the current node -->
<!ENTITY inspectorHTMLEdit.label "Edit As HTML">
<!ENTITY inspectorHTMLEdit.accesskey "E">
<!-- LOCALIZATION NOTE (inspectorCopyInnerHTML.label): This is the label shown
in the inspector contextual-menu for the item that lets users copy the
inner HTML of the current node -->
<!ENTITY inspectorCopyInnerHTML.label "Inner HTML">
<!ENTITY inspectorCopyInnerHTML.accesskey "I">
<!-- LOCALIZATION NOTE (inspectorCopyOuterHTML.label): This is the label shown
in the inspector contextual-menu for the item that lets users copy the
outer HTML of the current node -->
<!ENTITY inspectorCopyOuterHTML.label "Outer HTML">
<!ENTITY inspectorCopyOuterHTML.accesskey "O">
<!-- LOCALIZATION NOTE (inspectorCopyCSSSelector.label): This is the label
shown in the inspector contextual-menu for the item that lets users copy
the CSS Selector of the current node -->
<!ENTITY inspectorCopyCSSSelector.label "CSS Selector">
<!ENTITY inspectorCopyCSSSelector.accesskey "S">
<!-- LOCALIZATION NOTE (inspectorPasteOuterHTML.label): This is the label shown
in the inspector contextual-menu for the item that lets users paste outer
HTML in the current node -->
<!ENTITY inspectorPasteOuterHTML.label "Outer HTML">
<!ENTITY inspectorPasteOuterHTML.accesskey "O">
<!-- LOCALIZATION NOTE (inspectorPasteInnerHTML.label): This is the label shown
in the inspector contextual-menu for the item that lets users paste inner
HTML in the current node -->
<!ENTITY inspectorPasteInnerHTML.label "Inner HTML">
<!ENTITY inspectorPasteInnerHTML.accesskey "I">
<!-- LOCALIZATION NOTE (inspectorHTMLPasteExtraSubmenu.label): This is the label
shown in the inspector contextual-menu for the sub-menu of the other Paste
items, which allow to paste HTML:
- before the current node
- after the current node
- as the first child of the current node
- as the last child of the current node -->
<!ENTITY inspectorHTMLPasteExtraSubmenu.label "Paste…">
<!ENTITY inspectorHTMLPasteExtraSubmenu.accesskey "t">
<!-- LOCALIZATION NOTE (inspectorHTMLPasteBefore.label): This is the label shown
in the inspector contextual-menu for the item that lets users paste
the HTML before the current node -->
<!ENTITY inspectorHTMLPasteBefore.label "Before">
<!ENTITY inspectorHTMLPasteBefore.accesskey "B">
<!-- LOCALIZATION NOTE (inspectorHTMLPasteAfter.label): This is the label shown
in the inspector contextual-menu for the item that lets users paste
the HTML after the current node -->
<!ENTITY inspectorHTMLPasteAfter.label "After">
<!ENTITY inspectorHTMLPasteAfter.accesskey "A">
<!-- LOCALIZATION NOTE (inspectorHTMLPasteFirstChild.label): This is the label
shown in the inspector contextual-menu for the item that lets users paste
the HTML as the first child the current node -->
<!ENTITY inspectorHTMLPasteFirstChild.label "As First Child">
<!ENTITY inspectorHTMLPasteFirstChild.accesskey "F">
<!-- LOCALIZATION NOTE (inspectorHTMLPasteLastChild.label): This is the label
shown in the inspector contextual-menu for the item that lets users paste
the HTML as the last child the current node -->
<!ENTITY inspectorHTMLPasteLastChild.label "As Last Child">
<!ENTITY inspectorHTMLPasteLastChild.accesskey "L">
<!-- LOCALIZATION NOTE (inspectorScrollNodeIntoView.label): This is the label
shown in the inspector contextual-menu for the item that lets users scroll
the current node into view -->
<!ENTITY inspectorScrollNodeIntoView.label "Scroll Into View">
<!ENTITY inspectorScrollNodeIntoView.accesskey "S">
<!-- LOCALIZATION NOTE (inspectorHTMLDelete.label): This is the label shown in
the inspector contextual-menu for the item that lets users delete the
current node -->
<!ENTITY inspectorHTMLDelete.label "Delete Node">
<!ENTITY inspectorHTMLDelete.accesskey "D">
<!-- LOCALIZATION NOTE (inspectorAttributesSubmenu.label): This is the label
shown in the inspector contextual-menu for the sub-menu of the other
attribute items, which allow to:
- add new attribute
- edit attribute
- remove attribute -->
<!ENTITY inspectorAttributesSubmenu.label "Attributes">
<!ENTITY inspectorAttributesSubmenu.accesskey "A">
<!-- LOCALIZATION NOTE (inspectorAddAttribute.label): This is the label shown in
the inspector contextual-menu for the item that lets users add attribute
to current node -->
<!ENTITY inspectorAddAttribute.label "Add Attribute">
<!ENTITY inspectorAddAttribute.accesskey "A">
<!-- LOCALIZATION NOTE (inspectorEditAttribute.label): This is the label shown in
the inspector contextual-menu for the item that lets users edit attribute
for current node -->
<!ENTITY inspectorEditAttribute.label "Edit Attribute">
<!ENTITY inspectorEditAttribute.accesskey "E">
<!-- LOCALIZATION NOTE (inspectorRemoveAttribute.label): This is the label shown in <!-- LOCALIZATION NOTE (inspectorRemoveAttribute.label): This is the label shown in
the inspector contextual-menu for the item that lets users delete attribute the inspector contextual-menu for the item that lets users delete attribute
from current node --> from current node -->
@ -113,67 +10,8 @@
shown as the placeholder for the markup view search in the inspector. --> shown as the placeholder for the markup view search in the inspector. -->
<!ENTITY inspectorSearchHTML.label3 "Search HTML"> <!ENTITY inspectorSearchHTML.label3 "Search HTML">
<!-- LOCALIZATION NOTE (inspectorImageDataUri.label): This is the label
shown in the inspector contextual-menu for the item that lets users copy
the URL embedding the image data encoded in Base 64 (what we name
here Image Data URL). For more information:
https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs -->
<!ENTITY inspectorImageDataUri.label "Image Data-URL">
<!-- LOCALIZATION NOTE (inspectorShowDOMProperties.label): This is the label
shown in the inspector contextual-menu for the item that lets users see
the DOM properties of the current node. When triggered, this item
opens the split Console and displays the properties in its side panel. -->
<!ENTITY inspectorShowDOMProperties.label "Show DOM Properties">
<!-- LOCALIZATION NOTE (inspectorUseInConsole.label): This is the label
shown in the inspector contextual-menu for the item that outputs a
variable for the current node to the console. When triggered,
this item opens the split Console. -->
<!ENTITY inspectorUseInConsole.label "Use in Console">
<!-- LOCALIZATION NOTE (inspectorExpandNode.label): This is the label
shown in the inspector contextual-menu for recursively expanding
mark-up elements -->
<!ENTITY inspectorExpandNode.label "Expand All">
<!-- LOCALIZATION NOTE (inspectorCollapseNode.label): This is the label
shown in the inspector contextual-menu for recursively collapsing
mark-up elements -->
<!ENTITY inspectorCollapseNode.label "Collapse">
<!-- LOCALIZATION NOTE (inspectorScreenshotNode.label): This is the label
shown in the inspector contextual-menu for the item that lets users take
a screenshot of the currently selected node. -->
<!ENTITY inspectorScreenshotNode.label "Screenshot Node">
<!-- LOCALIZATION NOTE (inspectorDuplicateNode.label): This is the label
shown in the inspector contextual-menu for the item that lets users
duplicate the currently selected node. -->
<!ENTITY inspectorDuplicateNode.label "Duplicate Node">
<!-- LOCALIZATION NOTE (inspectorAddNode.label): This is the label shown in <!-- LOCALIZATION NOTE (inspectorAddNode.label): This is the label shown in
the inspector toolbar for the button that lets users add elements to the the inspector toolbar for the button that lets users add elements to the
DOM (as children of the currently selected element). --> DOM (as children of the currently selected element). -->
<!ENTITY inspectorAddNode.label "Create New Node"> <!ENTITY inspectorAddNode.label "Create New Node">
<!ENTITY inspectorAddNode.accesskey "C"> <!ENTITY inspectorAddNode.accesskey "C">
<!-- LOCALIZATION NOTE (inspectorCopyHTMLSubmenu.label): This is the label
shown in the inspector contextual-menu for the sub-menu of the other
copy items, which allow to:
- Copy Inner HTML
- Copy Outer HTML
- Copy Unique selector
- Copy Image data URI -->
<!ENTITY inspectorCopyHTMLSubmenu.label "Copy">
<!-- LOCALIZATION NOTE (inspectorPasteHTMLSubmenu.label): This is the label
shown in the inspector contextual-menu for the sub-menu of the other
paste items, which allow to:
- Paste Inner HTML
- Paste Outer HTML
- Before
- After
- As First Child
- As Last Child -->
<!ENTITY inspectorPasteHTMLSubmenu.label "Paste">

Просмотреть файл

@ -110,17 +110,19 @@ inspector.menu.copyUrlToClipboard.label=Copy Link Address
# select that element in the inspector. # select that element in the inspector.
inspector.menu.selectElement.label=Select Element #%S inspector.menu.selectElement.label=Select Element #%S
# LOCALIZATION NOTE (inspector.menu.editAttribute.label): This is the label of a # LOCALIZATION NOTE (inspectorEditAttribute.label): This is the label of a
# sub-menu "Attribute" in the inspector contextual-menu that appears # sub-menu "Attribute" in the inspector contextual-menu that appears
# when the user right-clicks on the node in the inspector, and that allows # when the user right-clicks on the node in the inspector, and that allows
# to edit an attribute on this node. # to edit an attribute on this node.
inspector.menu.editAttribute.label=Edit Attribute %S inspectorEditAttribute.label=Edit Attribute %S
inspectorEditAttribute.accesskey=E
# LOCALIZATION NOTE (inspector.menu.removeAttribute.label): This is the label of a # LOCALIZATION NOTE (inspectorRemoveAttribute.label): This is the label of a
# sub-menu "Attribute" in the inspector contextual-menu that appears # sub-menu "Attribute" in the inspector contextual-menu that appears
# when the user right-clicks on the attribute of a node in the inspector, # when the user right-clicks on the attribute of a node in the inspector,
# and that allows to remove this attribute. # and that allows to remove this attribute.
inspector.menu.removeAttribute.label=Remove Attribute %S inspectorRemoveAttribute.label=Remove Attribute %S
inspectorRemoveAttribute.accesskey=R
# LOCALIZATION NOTE (inspector.nodePreview.selectNodeLabel): # LOCALIZATION NOTE (inspector.nodePreview.selectNodeLabel):
# This string is displayed in a tooltip that is shown when hovering over a DOM # This string is displayed in a tooltip that is shown when hovering over a DOM
@ -141,6 +143,178 @@ inspector.nodePreview.selectNodeLabel=Click to select this node in the Inspector
# node in the page. # node in the page.
inspector.nodePreview.highlightNodeLabel=Click to highlight this node in the page inspector.nodePreview.highlightNodeLabel=Click to highlight this node in the page
# LOCALIZATION NOTE (inspectorHTMLEdit.label): This is the label shown
# in the inspector contextual-menu for the item that lets users edit the
# (outer) HTML of the current node
inspectorHTMLEdit.label=Edit As HTML
inspectorHTMLEdit.accesskey=E
# LOCALIZATION NOTE (inspectorCopyInnerHTML.label): This is the label shown
# in the inspector contextual-menu for the item that lets users copy the
# inner HTML of the current node
inspectorCopyInnerHTML.label=Inner HTML
inspectorCopyInnerHTML.accesskey=I
# LOCALIZATION NOTE (inspectorCopyOuterHTML.label): This is the label shown
# in the inspector contextual-menu for the item that lets users copy the
# outer HTML of the current node
inspectorCopyOuterHTML.label=Outer HTML
inspectorCopyOuterHTML.accesskey=O
# LOCALIZATION NOTE (inspectorCopyCSSSelector.label): This is the label
# shown in the inspector contextual-menu for the item that lets users copy
# the CSS Selector of the current node
inspectorCopyCSSSelector.label=CSS Selector
inspectorCopyCSSSelector.accesskey=S
# LOCALIZATION NOTE (inspectorPasteOuterHTML.label): This is the label shown
# in the inspector contextual-menu for the item that lets users paste outer
# HTML in the current node
inspectorPasteOuterHTML.label=Outer HTML
inspectorPasteOuterHTML.accesskey=O
# LOCALIZATION NOTE (inspectorPasteInnerHTML.label): This is the label shown
# in the inspector contextual-menu for the item that lets users paste inner
# HTML in the current node
inspectorPasteInnerHTML.label=Inner HTML
inspectorPasteInnerHTML.accesskey=I
# LOCALIZATION NOTE (inspectorHTMLPasteExtraSubmenu.label): This is the label
# shown in the inspector contextual-menu for the sub-menu of the other Paste
# items, which allow to paste HTML:
# - before the current node
# - after the current node
# - as the first child of the current node
# - as the last child of the current node
inspectorHTMLPasteExtraSubmenu.label=Paste…
inspectorHTMLPasteExtraSubmenu.accesskey=t
# LOCALIZATION NOTE (inspectorHTMLPasteBefore.label): This is the label shown
# in the inspector contextual-menu for the item that lets users paste
# the HTML before the current node
inspectorHTMLPasteBefore.label=Before
inspectorHTMLPasteBefore.accesskey=B
# LOCALIZATION NOTE (inspectorHTMLPasteAfter.label): This is the label shown
# in the inspector contextual-menu for the item that lets users paste
# the HTML after the current node
inspectorHTMLPasteAfter.label=After
inspectorHTMLPasteAfter.accesskey=A
# LOCALIZATION NOTE (inspectorHTMLPasteFirstChild.label): This is the label
# shown in the inspector contextual-menu for the item that lets users paste
# the HTML as the first child the current node
inspectorHTMLPasteFirstChild.label=As First Child
inspectorHTMLPasteFirstChild.accesskey=F
# LOCALIZATION NOTE (inspectorHTMLPasteLastChild.label): This is the label
# shown in the inspector contextual-menu for the item that lets users paste
# the HTML as the last child the current node
inspectorHTMLPasteLastChild.label=As Last Child
inspectorHTMLPasteLastChild.accesskey=L
# LOCALIZATION NOTE (inspectorScrollNodeIntoView.label): This is the label
# shown in the inspector contextual-menu for the item that lets users scroll
# the current node into view
inspectorScrollNodeIntoView.label=Scroll Into View
inspectorScrollNodeIntoView.accesskey=S
# LOCALIZATION NOTE (inspectorHTMLDelete.label): This is the label shown in
# the inspector contextual-menu for the item that lets users delete the
# current node
inspectorHTMLDelete.label=Delete Node
inspectorHTMLDelete.accesskey=D
# LOCALIZATION NOTE (inspectorAttributesSubmenu.label): This is the label
# shown in the inspector contextual-menu for the sub-menu of the other
# attribute items, which allow to:
# - add new attribute
# - edit attribute
# - remove attribute
inspectorAttributesSubmenu.label=Attributes
inspectorAttributesSubmenu.accesskey=A
# LOCALIZATION NOTE (inspectorAddAttribute.label): This is the label shown in
# the inspector contextual-menu for the item that lets users add attribute
# to current node
inspectorAddAttribute.label=Add Attribute
inspectorAddAttribute.accesskey=A
# LOCALIZATION NOTE (inspectorSearchHTML.label2): This is the label shown as
# the placeholder in inspector search box
inspectorSearchHTML.label2=Search with CSS Selectors
inspectorSearchHTML.key=F
# LOCALIZATION NOTE (inspectorSearchHTML.label3): This is the label that is
# shown as the placeholder for the markup view search in the inspector.
inspectorSearchHTML.label3=Search HTML
# LOCALIZATION NOTE (inspectorImageDataUri.label): This is the label
# shown in the inspector contextual-menu for the item that lets users copy
# the URL embedding the image data encoded in Base 64 (what we name
# here Image Data URL). For more information:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs
inspectorImageDataUri.label=Image Data-URL
# LOCALIZATION NOTE (inspectorShowDOMProperties.label): This is the label
# shown in the inspector contextual-menu for the item that lets users see
# the DOM properties of the current node. When triggered, this item
# opens the split Console and displays the properties in its side panel.
inspectorShowDOMProperties.label=Show DOM Properties
# LOCALIZATION NOTE (inspectorUseInConsole.label): This is the label
# shown in the inspector contextual-menu for the item that outputs a
# variable for the current node to the console. When triggered,
# this item opens the split Console.
inspectorUseInConsole.label=Use in Console
# LOCALIZATION NOTE (inspectorExpandNode.label): This is the label
# shown in the inspector contextual-menu for recursively expanding
# mark-up elements
inspectorExpandNode.label=Expand All
# LOCALIZATION NOTE (inspectorCollapseNode.label): This is the label
# shown in the inspector contextual-menu for recursively collapsing
# mark-up elements
inspectorCollapseNode.label=Collapse
# LOCALIZATION NOTE (inspectorScreenshotNode.label): This is the label
# shown in the inspector contextual-menu for the item that lets users take
# a screenshot of the currently selected node.
inspectorScreenshotNode.label=Screenshot Node
# LOCALIZATION NOTE (inspectorDuplicateNode.label): This is the label
# shown in the inspector contextual-menu for the item that lets users
# duplicate the currently selected node.
inspectorDuplicateNode.label=Duplicate Node
# LOCALIZATION NOTE (inspectorAddNode.label): This is the label shown in
# the inspector toolbar for the button that lets users add elements to the
# DOM (as children of the currently selected element).
inspectorAddNode.label=Create New Node
inspectorAddNode.accesskey=C
# LOCALIZATION NOTE (inspectorCopyHTMLSubmenu.label): This is the label
# shown in the inspector contextual-menu for the sub-menu of the other
# copy items, which allow to:
# - Copy Inner HTML
# - Copy Outer HTML
# - Copy Unique selector
# - Copy Image data URI
inspectorCopyHTMLSubmenu.label=Copy
# LOCALIZATION NOTE (inspectorPasteHTMLSubmenu.label): This is the label
# shown in the inspector contextual-menu for the sub-menu of the other
# paste items, which allow to:
# - Paste Inner HTML
# - Paste Outer HTML
# - Before
# - After
# - As First Child
# - As Last Child
inspectorPasteHTMLSubmenu.label=Paste
# LOCALIZATION NOTE (inspector.searchHTML.key): # LOCALIZATION NOTE (inspector.searchHTML.key):
# Key shortcut used to focus the DOM element search box on top-right corner of # Key shortcut used to focus the DOM element search box on top-right corner of
# the markup view # the markup view

Просмотреть файл

@ -3,7 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */ * You can obtain one at http://mozilla.org/MPL/2.0/. */
const { assert } = require("devtools/shared/DevToolsUtils"); const { assert } = require("devtools/shared/DevToolsUtils");
const { MemoryFront } = require("devtools/server/actors/memory"); const { MemoryFront } = require("devtools/shared/fronts/memory");
const HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient"); const HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient");
const { PropTypes } = require("devtools/client/shared/vendor/react"); const { PropTypes } = require("devtools/client/shared/vendor/react");
const { const {

Просмотреть файл

@ -7,7 +7,7 @@
const { Cc, Ci, Cu, Cr } = require("chrome"); const { Cc, Ci, Cu, Cr } = require("chrome");
const { Task } = require("devtools/shared/task"); const { Task } = require("devtools/shared/task");
const EventEmitter = require("devtools/shared/event-emitter"); const EventEmitter = require("devtools/shared/event-emitter");
const { MemoryFront } = require("devtools/server/actors/memory"); const { MemoryFront } = require("devtools/shared/fronts/memory");
const HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient"); const HeapAnalysesClient = require("devtools/shared/heapsnapshot/HeapAnalysesClient");
const promise = require("promise"); const promise = require("promise");

Просмотреть файл

@ -1,7 +1,6 @@
/* Any copyright is dedicated to the Public Domain. /* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from helper_html_tooltip.js */ /* import-globals-from helper_html_tooltip.js */
"use strict"; "use strict";
/** /**
@ -32,13 +31,14 @@ add_task(function* () {
yield addTab("about:blank"); yield addTab("about:blank");
let [,, doc] = yield createHost("bottom", TEST_URI); let [,, doc] = yield createHost("bottom", TEST_URI);
yield testTooltipNotClosingOnInsideClick(doc); yield testClickInTooltipContent(doc);
yield testConsumeOutsideClicksFalse(doc); yield testConsumeOutsideClicksFalse(doc);
yield testConsumeOutsideClicksTrue(doc); yield testConsumeOutsideClicksTrue(doc);
yield testClickInsideIframe(doc); yield testClickInOuterIframe(doc);
yield testClickInInnerIframe(doc);
}); });
function* testTooltipNotClosingOnInsideClick(doc) { function* testClickInTooltipContent(doc) {
info("Test a tooltip is not closed when clicking inside itself"); info("Test a tooltip is not closed when clicking inside itself");
let tooltip = new HTMLTooltip({doc}, {}); let tooltip = new HTMLTooltip({doc}, {});
@ -94,8 +94,8 @@ function* testConsumeOutsideClicksTrue(doc) {
tooltip.destroy(); tooltip.destroy();
} }
function* testClickInsideIframe(doc) { function* testClickInOuterIframe(doc) {
info("Test closing a tooltip via click inside an iframe"); info("Test clicking an iframe outside of the tooltip closes the tooltip");
let frame = doc.getElementById("frame"); let frame = doc.getElementById("frame");
let tooltip = new HTMLTooltip({doc}); let tooltip = new HTMLTooltip({doc});
@ -110,6 +110,26 @@ function* testClickInsideIframe(doc) {
tooltip.destroy(); tooltip.destroy();
} }
function* testClickInInnerIframe(doc) {
info("Test clicking an iframe inside the tooltip content does not close the tooltip");
let tooltip = new HTMLTooltip({doc}, {consumeOutsideClicks: false});
let iframe = doc.createElementNS(HTML_NS, "iframe");
iframe.style.width = "100px";
iframe.style.height = "50px";
yield tooltip.setContent(iframe, 100, 50);
yield showTooltip(tooltip, doc.getElementById("box1"));
let onTooltipContainerClick = once(tooltip.container, "click");
EventUtils.synthesizeMouseAtCenter(tooltip.container, {}, doc.defaultView);
yield onTooltipContainerClick;
is(tooltip.isVisible(), true, "Tooltip is still visible");
tooltip.destroy();
}
function getTooltipContent(doc) { function getTooltipContent(doc) {
let div = doc.createElementNS(HTML_NS, "div"); let div = doc.createElementNS(HTML_NS, "div");
div.style.height = "50px"; div.style.height = "50px";

Просмотреть файл

@ -250,10 +250,12 @@ HTMLTooltip.prototype = {
// Check if the node window is in the tooltip container. // Check if the node window is in the tooltip container.
while (win.parent && win.parent != win) { while (win.parent && win.parent != win) {
win = win.parent; if (win.parent === tooltipWindow) {
if (win === tooltipWindow) { // If the parent window is the tooltip window, check if the tooltip contains
// the current frame element.
return this.panel.contains(win.frameElement); return this.panel.contains(win.frameElement);
} }
win = win.parent;
} }
return false; return false;
@ -323,7 +325,7 @@ HTMLTooltip.prototype = {
if (arrowCenter > anchorCenter) { if (arrowCenter > anchorCenter) {
left = Math.max(0, left - (arrowCenter - anchorCenter)); left = Math.max(0, left - (arrowCenter - anchorCenter));
} }
// Arrow's feft offset relative to the anchor. // Arrow's left offset relative to the anchor.
arrowLeft = Math.min(ARROW_OFFSET, (anchorWidth - ARROW_WIDTH) / 2) | 0; arrowLeft = Math.min(ARROW_OFFSET, (anchorWidth - ARROW_WIDTH) / 2) | 0;
// Translate the coordinate to tooltip container // Translate the coordinate to tooltip container
arrowLeft += anchorLeft - left; arrowLeft += anchorLeft - left;

Просмотреть файл

@ -8,7 +8,6 @@
const TEST_URI = ` const TEST_URI = `
<style type='text/css'> <style type='text/css'>
div { background-color: seagreen; }
</style> </style>
<div id='testid' class='testclass'>Styled Node</div> <div id='testid' class='testclass'>Styled Node</div>
`; `;

Просмотреть файл

@ -31,28 +31,12 @@ const {method, Arg} = protocol;
const events = require("sdk/event/core"); const events = require("sdk/event/core");
const Heritage = require("sdk/core/heritage"); const Heritage = require("sdk/core/heritage");
const EventEmitter = require("devtools/shared/event-emitter"); const EventEmitter = require("devtools/shared/event-emitter");
const {reflowSpec} = require("devtools/shared/specs/layout");
/** /**
* The reflow actor tracks reflows and emits events about them. * The reflow actor tracks reflows and emits events about them.
*/ */
var ReflowActor = exports.ReflowActor = protocol.ActorClass({ var ReflowActor = exports.ReflowActor = protocol.ActorClassWithSpec(reflowSpec, {
typeName: "reflow",
events: {
/**
* The reflows event is emitted when reflows have been detected. The event
* is sent with an array of reflows that occured. Each item has the
* following properties:
* - start {Number}
* - end {Number}
* - isInterruptible {Boolean}
*/
"reflows": {
type: "reflows",
reflows: Arg(0, "array:json")
}
},
initialize: function (conn, tabActor) { initialize: function (conn, tabActor) {
protocol.Actor.prototype.initialize.call(this, conn); protocol.Actor.prototype.initialize.call(this, conn);
@ -85,24 +69,24 @@ var ReflowActor = exports.ReflowActor = protocol.ActorClass({
* This is a oneway method, do not expect a response and it won't return a * This is a oneway method, do not expect a response and it won't return a
* promise. * promise.
*/ */
start: method(function () { start: function () {
if (!this._isStarted) { if (!this._isStarted) {
this.observer.on("reflows", this._onReflow); this.observer.on("reflows", this._onReflow);
this._isStarted = true; this._isStarted = true;
} }
}, {oneway: true}), },
/** /**
* Stop tracking reflows and sending events to clients about them. * Stop tracking reflows and sending events to clients about them.
* This is a oneway method, do not expect a response and it won't return a * This is a oneway method, do not expect a response and it won't return a
* promise. * promise.
*/ */
stop: method(function () { stop: function () {
if (this._isStarted) { if (this._isStarted) {
this.observer.off("reflows", this._onReflow); this.observer.off("reflows", this._onReflow);
this._isStarted = false; this._isStarted = false;
} }
}, {oneway: true}), },
_onReflow: function (event, reflows) { _onReflow: function (event, reflows) {
if (this._isStarted) { if (this._isStarted) {
@ -111,25 +95,6 @@ var ReflowActor = exports.ReflowActor = protocol.ActorClass({
} }
}); });
/**
* Usage example of the reflow front:
*
* let front = ReflowFront(toolbox.target.client, toolbox.target.form);
* front.on("reflows", this._onReflows);
* front.start();
* // now wait for events to come
*/
exports.ReflowFront = protocol.FrontClass(ReflowActor, {
initialize: function (client, {reflowActor}) {
protocol.Front.prototype.initialize.call(this, client, {actor: reflowActor});
this.manage(this);
},
destroy: function () {
protocol.Front.prototype.destroy.call(this);
},
});
/** /**
* Base class for all sorts of observers that need to listen to events on the * Base class for all sorts of observers that need to listen to events on the
* tabActor's windows. * tabActor's windows.

Просмотреть файл

@ -4,33 +4,13 @@
"use strict"; "use strict";
const { Cc, Ci, Cu, components } = require("chrome");
const protocol = require("devtools/shared/protocol"); const protocol = require("devtools/shared/protocol");
const { method, RetVal, Arg, types } = protocol;
const { Memory } = require("devtools/server/performance/memory"); const { Memory } = require("devtools/server/performance/memory");
const { actorBridge } = require("devtools/server/actors/common"); const { actorBridgeWithSpec } = require("devtools/server/actors/common");
const { Task } = require("devtools/shared/task"); const { memorySpec } = require("devtools/shared/specs/memory");
loader.lazyRequireGetter(this, "events", "sdk/event/core"); loader.lazyRequireGetter(this, "events", "sdk/event/core");
loader.lazyRequireGetter(this, "StackFrameCache", loader.lazyRequireGetter(this, "StackFrameCache",
"devtools/server/actors/utils/stack", true); "devtools/server/actors/utils/stack", true);
loader.lazyRequireGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm", true);
loader.lazyRequireGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm", true);
loader.lazyRequireGetter(this, "HeapSnapshotFileUtils",
"devtools/shared/heapsnapshot/HeapSnapshotFileUtils");
loader.lazyRequireGetter(this, "ThreadSafeChromeUtils");
types.addDictType("AllocationsRecordingOptions", {
// The probability we sample any given allocation when recording
// allocations. Must be between 0.0 and 1.0. Defaults to 1.0, or sampling
// every allocation.
probability: "number",
// The maximum number of of allocation events to keep in the allocations
// log. If new allocations arrive, when we are already at capacity, the oldest
// allocation event is lost. This number must fit in a 32 bit signed integer.
maxLogLength: "number"
});
/** /**
* An actor that returns memory usage data for its parent actor's window. * An actor that returns memory usage data for its parent actor's window.
@ -43,31 +23,7 @@ types.addDictType("AllocationsRecordingOptions", {
* *
* @see devtools/server/performance/memory.js for documentation. * @see devtools/server/performance/memory.js for documentation.
*/ */
var MemoryActor = exports.MemoryActor = protocol.ActorClass({ exports.MemoryActor = protocol.ActorClassWithSpec(memorySpec, {
typeName: "memory",
/**
* The set of unsolicited events the MemoryActor emits that will be sent over
* the RDP (by protocol.js).
*/
events: {
// Same format as the data passed to the
// `Debugger.Memory.prototype.onGarbageCollection` hook. See
// `js/src/doc/Debugger/Debugger.Memory.md` for documentation.
"garbage-collection": {
type: "garbage-collection",
data: Arg(0, "json"),
},
// Same data as the data from `getAllocations` -- only fired if
// `autoDrain` set during `startRecordingAllocations`.
"allocations": {
type: "allocations",
data: Arg(0, "json"),
},
},
initialize: function (conn, parent, frameCache = new StackFrameCache()) { initialize: function (conn, parent, frameCache = new StackFrameCache()) {
protocol.Actor.prototype.initialize.call(this, conn); protocol.Actor.prototype.initialize.call(this, conn);
@ -85,88 +41,33 @@ var MemoryActor = exports.MemoryActor = protocol.ActorClass({
protocol.Actor.prototype.destroy.call(this); protocol.Actor.prototype.destroy.call(this);
}, },
attach: actorBridge("attach", { attach: actorBridgeWithSpec("attach"),
request: {},
response: {
type: "attached"
}
}),
detach: actorBridge("detach", { detach: actorBridgeWithSpec("detach"),
request: {},
response: {
type: "detached"
}
}),
getState: actorBridge("getState", { getState: actorBridgeWithSpec("getState"),
response: {
state: RetVal(0, "string")
}
}),
saveHeapSnapshot: method(function () { saveHeapSnapshot: function () {
return this.bridge.saveHeapSnapshot(); return this.bridge.saveHeapSnapshot();
}, { },
response: {
snapshotId: RetVal("string")
}
}),
takeCensus: actorBridge("takeCensus", { takeCensus: actorBridgeWithSpec("takeCensus"),
request: {},
response: RetVal("json")
}),
startRecordingAllocations: actorBridge("startRecordingAllocations", { startRecordingAllocations: actorBridgeWithSpec("startRecordingAllocations"),
request: {
options: Arg(0, "nullable:AllocationsRecordingOptions")
},
response: {
// Accept `nullable` in the case of server Gecko <= 37, handled on the front
value: RetVal(0, "nullable:number")
}
}),
stopRecordingAllocations: actorBridge("stopRecordingAllocations", { stopRecordingAllocations: actorBridgeWithSpec("stopRecordingAllocations"),
request: {},
response: {
// Accept `nullable` in the case of server Gecko <= 37, handled on the front
value: RetVal(0, "nullable:number")
}
}),
getAllocationsSettings: actorBridge("getAllocationsSettings", { getAllocationsSettings: actorBridgeWithSpec("getAllocationsSettings"),
request: {},
response: {
options: RetVal(0, "json")
}
}),
getAllocations: actorBridge("getAllocations", { getAllocations: actorBridgeWithSpec("getAllocations"),
request: {},
response: RetVal("json")
}),
forceGarbageCollection: actorBridge("forceGarbageCollection", { forceGarbageCollection: actorBridgeWithSpec("forceGarbageCollection"),
request: {},
response: {}
}),
forceCycleCollection: actorBridge("forceCycleCollection", { forceCycleCollection: actorBridgeWithSpec("forceCycleCollection"),
request: {},
response: {}
}),
measure: actorBridge("measure", { measure: actorBridgeWithSpec("measure"),
request: {},
response: RetVal("json"),
}),
residentUnique: actorBridge("residentUnique", { residentUnique: actorBridgeWithSpec("residentUnique"),
request: {},
response: { value: RetVal("number") }
}),
_onGarbageCollection: function (data) { _onGarbageCollection: function (data) {
if (this.conn.transport) { if (this.conn.transport) {
@ -180,76 +81,3 @@ var MemoryActor = exports.MemoryActor = protocol.ActorClass({
} }
}, },
}); });
exports.MemoryFront = protocol.FrontClass(MemoryActor, {
initialize: function (client, form, rootForm = null) {
protocol.Front.prototype.initialize.call(this, client, form);
this._client = client;
this.actorID = form.memoryActor;
this.heapSnapshotFileActorID = rootForm
? rootForm.heapSnapshotFileActor
: null;
this.manage(this);
},
/**
* Save a heap snapshot, transfer it from the server to the client if the
* server and client do not share a file system, and return the local file
* path to the heap snapshot.
*
* Note that this is safe to call for actors inside sandoxed child processes,
* as we jump through the correct IPDL hoops.
*
* @params Boolean options.forceCopy
* Always force a bulk data copy of the saved heap snapshot, even when
* the server and client share a file system.
*
* @returns Promise<String>
*/
saveHeapSnapshot: protocol.custom(Task.async(function* (options = {}) {
const snapshotId = yield this._saveHeapSnapshotImpl();
if (!options.forceCopy &&
(yield HeapSnapshotFileUtils.haveHeapSnapshotTempFile(snapshotId))) {
return HeapSnapshotFileUtils.getHeapSnapshotTempFilePath(snapshotId);
}
return yield this.transferHeapSnapshot(snapshotId);
}), {
impl: "_saveHeapSnapshotImpl"
}),
/**
* Given that we have taken a heap snapshot with the given id, transfer the
* heap snapshot file to the client. The path to the client's local file is
* returned.
*
* @param {String} snapshotId
*
* @returns Promise<String>
*/
transferHeapSnapshot: protocol.custom(function (snapshotId) {
if (!this.heapSnapshotFileActorID) {
throw new Error("MemoryFront initialized without a rootForm");
}
const request = this._client.request({
to: this.heapSnapshotFileActorID,
type: "transferHeapSnapshot",
snapshotId
});
return new Promise((resolve, reject) => {
const outFilePath =
HeapSnapshotFileUtils.getNewUniqueHeapSnapshotTempFilePath();
const outFile = new FileUtils.File(outFilePath);
const outFileStream = FileUtils.openSafeFileOutputStream(outFile);
request.on("bulk-reply", Task.async(function* ({ copyTo }) {
yield copyTo(outFileStream);
FileUtils.closeSafeFileOutputStream(outFileStream);
resolve(outFilePath);
}));
});
})
});

Просмотреть файл

@ -75,7 +75,10 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
this.styleElements = new WeakMap(); this.styleElements = new WeakMap();
this.onFrameUnload = this.onFrameUnload.bind(this); this.onFrameUnload = this.onFrameUnload.bind(this);
this.onStyleSheetAdded = this.onStyleSheetAdded.bind(this);
events.on(this.inspector.tabActor, "will-navigate", this.onFrameUnload); events.on(this.inspector.tabActor, "will-navigate", this.onFrameUnload);
events.on(this.inspector.tabActor, "stylesheet-added", this.onStyleSheetAdded);
this._styleApplied = this._styleApplied.bind(this); this._styleApplied = this._styleApplied.bind(this);
this._watchedSheets = new Set(); this._watchedSheets = new Set();
@ -87,6 +90,7 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
} }
protocol.Actor.prototype.destroy.call(this); protocol.Actor.prototype.destroy.call(this);
events.off(this.inspector.tabActor, "will-navigate", this.onFrameUnload); events.off(this.inspector.tabActor, "will-navigate", this.onFrameUnload);
events.off(this.inspector.tabActor, "stylesheet-added", this.onStyleSheetAdded);
this.inspector = null; this.inspector = null;
this.walker = null; this.walker = null;
this.refMap = null; this.refMap = null;
@ -174,10 +178,6 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
_sheetRef: function (sheet) { _sheetRef: function (sheet) {
let tabActor = this.inspector.tabActor; let tabActor = this.inspector.tabActor;
let actor = tabActor.createStyleSheetActor(sheet); let actor = tabActor.createStyleSheetActor(sheet);
if (!this._watchedSheets.has(actor)) {
this._watchedSheets.add(actor);
actor.on("style-applied", this._styleApplied);
}
return actor; return actor;
}, },
@ -837,6 +837,18 @@ var PageStyleActor = protocol.ActorClassWithSpec(pageStyleSpec, {
this.styleElements = new WeakMap(); this.styleElements = new WeakMap();
}, },
/**
* When a stylesheet is added, handle the related StyleSheetActor to listen for changes.
* @param {StyleSheetActor} actor
* The actor for the added stylesheet.
*/
onStyleSheetAdded: function (actor) {
if (!this._watchedSheets.has(actor)) {
this._watchedSheets.add(actor);
actor.on("style-applied", this._styleApplied);
}
},
/** /**
* Helper function to addNewRule to get or create a style tag in the provided * Helper function to addNewRule to get or create a style tag in the provided
* document. * document.

Просмотреть файл

@ -814,6 +814,10 @@ exports.BrowserTabList = BrowserTabList;
* This event fires when we switch the TabActor targeted document * This event fires when we switch the TabActor targeted document
* to one of its iframes, or back to its original top document. * to one of its iframes, or back to its original top document.
* It is dispatched between window-destroyed and window-ready. * It is dispatched between window-destroyed and window-ready.
* - stylesheet-added
* This event is fired when a StyleSheetActor is created.
* It contains the following attribute :
* * actor (StyleSheetActor) The created actor.
* *
* Note that *all* these events are dispatched in the following order * Note that *all* these events are dispatched in the following order
* when we switch the context of the TabActor to a given iframe: * when we switch the context of the TabActor to a given iframe:
@ -2040,6 +2044,7 @@ TabActor.prototype = {
this._styleSheetActors.set(styleSheet, actor); this._styleSheetActors.set(styleSheet, actor);
this._tabPool.addActor(actor); this._tabPool.addActor(actor);
events.emit(this, "stylesheet-added", actor);
return actor; return actor;
}, },

Просмотреть файл

@ -8,7 +8,7 @@ var Services = require("Services");
var { DebuggerClient } = require("devtools/shared/client/main"); var { DebuggerClient } = require("devtools/shared/client/main");
var { DebuggerServer } = require("devtools/server/main"); var { DebuggerServer } = require("devtools/server/main");
var { MemoryFront } = require("devtools/server/actors/memory"); var { MemoryFront } = require("devtools/shared/fronts/memory");
// Always log packets when running tests. // Always log packets when running tests.
Services.prefs.setBoolPref("devtools.debugger.log", true); Services.prefs.setBoolPref("devtools.debugger.log", true);

Просмотреть файл

@ -38,7 +38,7 @@ const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { DebuggerServer } = require("devtools/server/main"); const { DebuggerServer } = require("devtools/server/main");
const { DebuggerServer: WorkerDebuggerServer } = worker.require("devtools/server/main"); const { DebuggerServer: WorkerDebuggerServer } = worker.require("devtools/server/main");
const { DebuggerClient, ObjectClient } = require("devtools/shared/client/main"); const { DebuggerClient, ObjectClient } = require("devtools/shared/client/main");
const { MemoryFront } = require("devtools/server/actors/memory"); const { MemoryFront } = require("devtools/shared/fronts/memory");
const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {}); const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});

Просмотреть файл

@ -0,0 +1,28 @@
/* 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 {reflowSpec} = require("devtools/shared/specs/layout");
const protocol = require("devtools/shared/protocol");
/**
* Usage example of the reflow front:
*
* let front = ReflowFront(toolbox.target.client, toolbox.target.form);
* front.on("reflows", this._onReflows);
* front.start();
* // now wait for events to come
*/
const ReflowFront = protocol.FrontClassWithSpec(reflowSpec, {
initialize: function (client, {reflowActor}) {
protocol.Front.prototype.initialize.call(this, client, {actor: reflowActor});
this.manage(this);
},
destroy: function () {
protocol.Front.prototype.destroy.call(this);
},
});
exports.ReflowFront = ReflowFront;

Просмотреть файл

@ -0,0 +1,88 @@
/* 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 { memorySpec } = require("devtools/shared/specs/memory");
const { Task } = require("devtools/shared/task");
const protocol = require("devtools/shared/protocol");
loader.lazyRequireGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm", true);
loader.lazyRequireGetter(this, "HeapSnapshotFileUtils",
"devtools/shared/heapsnapshot/HeapSnapshotFileUtils");
const MemoryFront = protocol.FrontClassWithSpec(memorySpec, {
initialize: function (client, form, rootForm = null) {
protocol.Front.prototype.initialize.call(this, client, form);
this._client = client;
this.actorID = form.memoryActor;
this.heapSnapshotFileActorID = rootForm
? rootForm.heapSnapshotFileActor
: null;
this.manage(this);
},
/**
* Save a heap snapshot, transfer it from the server to the client if the
* server and client do not share a file system, and return the local file
* path to the heap snapshot.
*
* Note that this is safe to call for actors inside sandoxed child processes,
* as we jump through the correct IPDL hoops.
*
* @params Boolean options.forceCopy
* Always force a bulk data copy of the saved heap snapshot, even when
* the server and client share a file system.
*
* @returns Promise<String>
*/
saveHeapSnapshot: protocol.custom(Task.async(function* (options = {}) {
const snapshotId = yield this._saveHeapSnapshotImpl();
if (!options.forceCopy &&
(yield HeapSnapshotFileUtils.haveHeapSnapshotTempFile(snapshotId))) {
return HeapSnapshotFileUtils.getHeapSnapshotTempFilePath(snapshotId);
}
return yield this.transferHeapSnapshot(snapshotId);
}), {
impl: "_saveHeapSnapshotImpl"
}),
/**
* Given that we have taken a heap snapshot with the given id, transfer the
* heap snapshot file to the client. The path to the client's local file is
* returned.
*
* @param {String} snapshotId
*
* @returns Promise<String>
*/
transferHeapSnapshot: protocol.custom(function (snapshotId) {
if (!this.heapSnapshotFileActorID) {
throw new Error("MemoryFront initialized without a rootForm");
}
const request = this._client.request({
to: this.heapSnapshotFileActorID,
type: "transferHeapSnapshot",
snapshotId
});
return new Promise((resolve, reject) => {
const outFilePath =
HeapSnapshotFileUtils.getNewUniqueHeapSnapshotTempFilePath();
const outFile = new FileUtils.File(outFilePath);
const outFileStream = FileUtils.openSafeFileOutputStream(outFile);
request.on("bulk-reply", Task.async(function* ({ copyTo }) {
yield copyTo(outFileStream);
FileUtils.closeSafeFileOutputStream(outFileStream);
resolve(outFilePath);
}));
});
})
});
exports.MemoryFront = MemoryFront;

Просмотреть файл

@ -18,6 +18,8 @@ DevToolsModules(
'gcli.js', 'gcli.js',
'highlighters.js', 'highlighters.js',
'inspector.js', 'inspector.js',
'layout.js',
'memory.js',
'preference.js', 'preference.js',
'promises.js', 'promises.js',
'settings.js', 'settings.js',

Просмотреть файл

@ -0,0 +1,32 @@
/* 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 {Arg, generateActorSpec} = require("devtools/shared/protocol");
const reflowSpec = generateActorSpec({
typeName: "reflow",
events: {
/**
* The reflows event is emitted when reflows have been detected. The event
* is sent with an array of reflows that occured. Each item has the
* following properties:
* - start {Number}
* - end {Number}
* - isInterruptible {Boolean}
*/
reflows: {
type: "reflows",
reflows: Arg(0, "array:json")
}
},
methods: {
start: {oneway: true},
stop: {oneway: true},
},
});
exports.reflowSpec = reflowSpec;

Просмотреть файл

@ -0,0 +1,121 @@
/* 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 {
Arg,
RetVal,
types,
generateActorSpec,
} = require("devtools/shared/protocol");
types.addDictType("AllocationsRecordingOptions", {
// The probability we sample any given allocation when recording
// allocations. Must be between 0.0 and 1.0. Defaults to 1.0, or sampling
// every allocation.
probability: "number",
// The maximum number of of allocation events to keep in the allocations
// log. If new allocations arrive, when we are already at capacity, the oldest
// allocation event is lost. This number must fit in a 32 bit signed integer.
maxLogLength: "number"
});
const memorySpec = generateActorSpec({
typeName: "memory",
/**
* The set of unsolicited events the MemoryActor emits that will be sent over
* the RDP (by protocol.js).
*/
events: {
// Same format as the data passed to the
// `Debugger.Memory.prototype.onGarbageCollection` hook. See
// `js/src/doc/Debugger/Debugger.Memory.md` for documentation.
"garbage-collection": {
type: "garbage-collection",
data: Arg(0, "json"),
},
// Same data as the data from `getAllocations` -- only fired if
// `autoDrain` set during `startRecordingAllocations`.
"allocations": {
type: "allocations",
data: Arg(0, "json"),
},
},
methods: {
attach: {
request: {},
response: {
type: "attached"
}
},
detach: {
request: {},
response: {
type: "detached"
}
},
getState: {
response: {
state: RetVal(0, "string")
}
},
takeCensus: {
request: {},
response: RetVal("json")
},
startRecordingAllocations: {
request: {
options: Arg(0, "nullable:AllocationsRecordingOptions")
},
response: {
// Accept `nullable` in the case of server Gecko <= 37, handled on the front
value: RetVal(0, "nullable:number")
}
},
stopRecordingAllocations: {
request: {},
response: {
// Accept `nullable` in the case of server Gecko <= 37, handled on the front
value: RetVal(0, "nullable:number")
}
},
getAllocationsSettings: {
request: {},
response: {
options: RetVal(0, "json")
}
},
getAllocations: {
request: {},
response: RetVal("json")
},
forceGarbageCollection: {
request: {},
response: {}
},
forceCycleCollection: {
request: {},
response: {}
},
measure: {
request: {},
response: RetVal("json"),
},
residentUnique: {
request: {},
response: { value: RetVal("number") }
},
saveHeapSnapshot: {
response: {
snapshotId: RetVal("string")
}
},
},
});
exports.memorySpec = memorySpec;

Просмотреть файл

@ -22,6 +22,8 @@ DevToolsModules(
'heap-snapshot-file.js', 'heap-snapshot-file.js',
'highlighters.js', 'highlighters.js',
'inspector.js', 'inspector.js',
'layout.js',
'memory.js',
'node.js', 'node.js',
'preference.js', 'preference.js',
'promises.js', 'promises.js',

Просмотреть файл

@ -893,7 +893,7 @@ pref("dom.serviceWorkers.openWindow.enabled", true);
pref("dom.push.debug", false); pref("dom.push.debug", false);
// The upstream autopush endpoint must have the Google API key corresponding to // The upstream autopush endpoint must have the Google API key corresponding to
// the App's sender ID; we bake this assumption directly into the URL. // the App's sender ID; we bake this assumption directly into the URL.
pref("dom.push.serverURL", "https://updates-autopush.stage.mozaws.net/v1/gcm/@MOZ_ANDROID_GCM_SENDERID@"); pref("dom.push.serverURL", "https://updates.push.services.mozilla.com/v1/gcm/@MOZ_ANDROID_GCM_SENDERID@");
pref("dom.push.maxRecentMessageIDsPerSubscription", 0); pref("dom.push.maxRecentMessageIDsPerSubscription", 0);
#ifdef MOZ_ANDROID_GCM #ifdef MOZ_ANDROID_GCM

Просмотреть файл

@ -11,8 +11,8 @@
<ImageView android:id="@+id/icon" <ImageView android:id="@+id/icon"
android:src="@drawable/folder_closed" android:src="@drawable/folder_closed"
android:layout_width="24dp" android:layout_width="24dp"
android:layout_height="24dp" android:layout_height="18dp"
android:scaleType="fitCenter" android:scaleType="fitXY"
android:layout_margin="20dp"/> android:layout_margin="20dp"/>
<LinearLayout android:layout_width="0dp" <LinearLayout android:layout_width="0dp"

Просмотреть файл

@ -7,4 +7,4 @@ ANDROID_PACKAGE_NAME=org.mozilla.fennec_aurora
MOZ_UPDATER=1 MOZ_UPDATER=1
MOZ_ANDROID_ANR_REPORTER=1 MOZ_ANDROID_ANR_REPORTER=1
MOZ_ANDROID_SHARED_ID=org.mozilla.fennec.sharedID MOZ_ANDROID_SHARED_ID=org.mozilla.fennec.sharedID
MOZ_ANDROID_GCM_SENDERID=829133274407 MOZ_ANDROID_GCM_SENDERID=965234145045

Просмотреть файл

@ -7,4 +7,4 @@ ANDROID_PACKAGE_NAME=org.mozilla.firefox_beta
MOZ_UPDATER= MOZ_UPDATER=
MOZ_ANDROID_ANR_REPORTER= MOZ_ANDROID_ANR_REPORTER=
MOZ_ANDROID_SHARED_ID=org.mozilla.firefox.sharedID MOZ_ANDROID_SHARED_ID=org.mozilla.firefox.sharedID
MOZ_ANDROID_GCM_SENDERID=829133274407 MOZ_ANDROID_GCM_SENDERID=965234145045

Просмотреть файл

@ -6,4 +6,4 @@ MOZ_APP_DISPLAYNAME=Nightly
MOZ_UPDATER=1 MOZ_UPDATER=1
MOZ_ANDROID_ANR_REPORTER=1 MOZ_ANDROID_ANR_REPORTER=1
MOZ_ANDROID_SHARED_ID=org.mozilla.fennec.sharedID MOZ_ANDROID_SHARED_ID=org.mozilla.fennec.sharedID
MOZ_ANDROID_GCM_SENDERID=829133274407 MOZ_ANDROID_GCM_SENDERID=965234145045

Просмотреть файл

@ -7,4 +7,4 @@ ANDROID_PACKAGE_NAME=org.mozilla.firefox
MOZ_UPDATER= MOZ_UPDATER=
MOZ_ANDROID_ANR_REPORTER= MOZ_ANDROID_ANR_REPORTER=
MOZ_ANDROID_SHARED_ID=org.mozilla.firefox.sharedID MOZ_ANDROID_SHARED_ID=org.mozilla.firefox.sharedID
MOZ_ANDROID_GCM_SENDERID=829133274407 MOZ_ANDROID_GCM_SENDERID=965234145045

Просмотреть файл

@ -6,4 +6,4 @@ ANDROID_PACKAGE_NAME=org.mozilla.fennec_`echo $USER | sed 's/-/_/g'`
MOZ_APP_DISPLAYNAME="Fennec `echo $USER | sed 's/-/_/g'`" MOZ_APP_DISPLAYNAME="Fennec `echo $USER | sed 's/-/_/g'`"
MOZ_UPDATER= MOZ_UPDATER=
MOZ_ANDROID_ANR_REPORTER= MOZ_ANDROID_ANR_REPORTER=
MOZ_ANDROID_GCM_SENDERID=829133274407 MOZ_ANDROID_GCM_SENDERID=965234145045

Просмотреть файл

@ -28,7 +28,7 @@ alertDownloadsCancel=Cancel
# LOCALIZATION NOTE (alertDownloadSucceeded): This text is shown as a snackbar inside the app after a # LOCALIZATION NOTE (alertDownloadSucceeded): This text is shown as a snackbar inside the app after a
# successful download. %S will be replaced by the file name of the download. # successful download. %S will be replaced by the file name of the download.
alertDownloadSucceeded=%S downloaded alertDownloadSucceeded=%S downloaded
# LOCALIZATION NOTE (undoCloseToast.message): This message appears in a toast # LOCALIZATION NOTE (downloads.disabledInGuest): This message appears in a toast
# when the user tries to download something in Guest mode. # when the user tries to download something in Guest mode.
downloads.disabledInGuest=Downloads are disabled in guest sessions downloads.disabledInGuest=Downloads are disabled in guest sessions

Просмотреть файл

@ -18,10 +18,13 @@ import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.Toast; import android.widget.Toast;
import org.mozilla.gecko.AppConstants; import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.Locales.LocaleAwareAppCompatActivity; import org.mozilla.gecko.Locales.LocaleAwareAppCompatActivity;
@ -207,4 +210,23 @@ public class FxAccountStatusActivity extends LocaleAwareAppCompatActivity {
} }
return super.onCreateOptionsMenu(menu); return super.onCreateOptionsMenu(menu);
}; };
@Override
public void openOptionsMenu() {
// This is a workaround of an Android bug:
// https://code.google.com/p/android/issues/detail?id=185217
// openOptionsMenu isn't overriden by WindowDecorActionBar, which is used by AppCompatActivity,
// meaning getSupportActionbar().openOptionsMenu doesn't work.
// Based loosely on the code in:
// http://androidxref.com/6.0.1_r10/xref/frameworks/support/v7/appcompat/src/android/support/v7/internal/app/WindowDecorActionBar.java#getDecorToolbar
final Window window = getWindow();
final View decor = window.getDecorView();
final View view = decor.findViewById(R.id.action_bar);
if (view instanceof Toolbar) {
final Toolbar toolbar = (Toolbar) view;
toolbar.showOverflowMenu();
}
}
} }

Просмотреть файл

@ -35,46 +35,57 @@ public class testUnifiedTelemetryClientId extends JavascriptBridgeTest {
getFHRClientIdFile(), getFHRClientIdFile(),
getFHRClientIdParentDir(), getFHRClientIdParentDir(),
}; };
// In local testing, it's possible to ^C out of the harness and not have tearDown called,
// hence reset. We can't clear the cache because Gecko is not running yet.
resetTest(false);
} }
public void tearDown() throws Exception { public void tearDown() throws Exception {
// Don't clear cache because who knows what state Gecko is in. // Don't clear cache because who knows what state Gecko is in.
resetTest(false); deleteClientIDFiles();
super.tearDown(); super.tearDown();
} }
private void resetTest(final boolean resetJSCache) { private void deleteClientIDFiles() {
Log.d(LOGTAG, "resetTest: begin"); Log.d(LOGTAG, "deleteClientIDFiles: begin");
if (resetJSCache) {
resetJSCache();
}
for (final File file : filesToDeleteOnReset) { for (final File file : filesToDeleteOnReset) {
file.delete(); file.delete(); // can't check return value because the file may not exist before deletion.
fAssertFalse("Deleted file in reset does not exist", file.exists()); // sanity check. fAssertFalse("Deleted file in reset does not exist", file.exists()); // sanity check.
} }
Log.d(LOGTAG, "resetTest: end"); Log.d(LOGTAG, "deleteClientIDFiles: end");
} }
public void testUnifiedTelemetryClientId() throws Exception { public void testUnifiedTelemetryClientId() throws Exception {
blockForReadyAndLoadJS(TEST_JS); blockForReadyAndLoadJS(TEST_JS);
resetJSCache(); // Must be called after Gecko is loaded.
fAssertTrue("Profile directory exists", profileDir.exists()); fAssertTrue("Profile directory exists", profileDir.exists());
// Important note: we cannot stop Gecko from running while we run this test and
// Gecko is capable of creating client ID files while we run this test. However,
// ClientID.jsm will not touch modify the client ID files on disk if its client
// ID cache is filled. As such, we prevent it from touching the disk by intentionally
// priming the cache & deleting the files it added now, and resetting the cache at the
// latest possible moment before we attempt to test the client ID file.
//
// This is fragile because it relies on the ClientID cache's implementation, however,
// some alternatives (e.g. changing file system permissions, file locking) are worse
// because they can fire error handling code, which is not currently under test.
//
// First, we delete the test files - we don't want the cache prime to fail which could happen if
// these files are around & corrupted from a previous test/install. Then we prime the cache,
// and delete the files the cache priming added, so the tests are ready to add their own version
// of these files.
deleteClientIDFiles();
primeJsClientIdCache();
deleteClientIDFiles();
// TODO: If these tests weren't so expensive to run in automation, // TODO: If these tests weren't so expensive to run in automation,
// this should be two separate tests to avoid storing state between tests. // this should be two separate tests to avoid storing state between tests.
testJavaCreatesClientId(); testJavaCreatesClientId(); // leaves cache filled.
resetTest(true); deleteClientIDFiles();
testJsCreatesClientId(); testJsCreatesClientId(); // leaves cache filled.
resetTest(true); deleteClientIDFiles();
testJavaMigratesFromHealthReport(); testJavaMigratesFromHealthReport(); // leaves cache filled.
resetTest(true); deleteClientIDFiles();
testJsMigratesFromHealthReport(); testJsMigratesFromHealthReport(); // leaves cache filled.
getJS().syncCall("endTest"); getJS().syncCall("endTest");
} }
@ -92,6 +103,7 @@ public class testUnifiedTelemetryClientId extends JavascriptBridgeTest {
fAssertFalse("Client id file does not exist yet", getClientIdFile().exists()); fAssertFalse("Client id file does not exist yet", getClientIdFile().exists());
final String clientIdFromJava = getClientIdFromJava(); final String clientIdFromJava = getClientIdFromJava();
resetJSCache();
final String clientIdFromJS = getClientIdFromJS(); final String clientIdFromJS = getClientIdFromJS();
fAssertEquals("Client ID from Java equals ID from JS", clientIdFromJava, clientIdFromJS); fAssertEquals("Client ID from Java equals ID from JS", clientIdFromJava, clientIdFromJS);
@ -116,6 +128,7 @@ public class testUnifiedTelemetryClientId extends JavascriptBridgeTest {
fAssertFalse("Client id file does not exist yet", getClientIdFile().exists()); fAssertFalse("Client id file does not exist yet", getClientIdFile().exists());
resetJSCache();
final String clientIdFromJS = getClientIdFromJS(); final String clientIdFromJS = getClientIdFromJS();
final String clientIdFromJava = getClientIdFromJava(); final String clientIdFromJava = getClientIdFromJava();
fAssertEquals("Client ID from JS equals ID from Java", clientIdFromJS, clientIdFromJava); fAssertEquals("Client ID from JS equals ID from Java", clientIdFromJS, clientIdFromJava);
@ -148,6 +161,7 @@ public class testUnifiedTelemetryClientId extends JavascriptBridgeTest {
final String clientIdFromJava = getClientIdFromJava(); final String clientIdFromJava = getClientIdFromJava();
fAssertEquals("Health report client ID merged by Java", expectedClientId, clientIdFromJava); fAssertEquals("Health report client ID merged by Java", expectedClientId, clientIdFromJava);
resetJSCache();
final String clientIdFromJS = getClientIdFromJS(); final String clientIdFromJS = getClientIdFromJS();
fAssertEquals("Merged client ID read by JS", expectedClientId, clientIdFromJS); fAssertEquals("Merged client ID read by JS", expectedClientId, clientIdFromJS);
@ -177,6 +191,7 @@ public class testUnifiedTelemetryClientId extends JavascriptBridgeTest {
final String expectedClientId = UUID.randomUUID().toString(); final String expectedClientId = UUID.randomUUID().toString();
createFHRClientIdFile(expectedClientId); createFHRClientIdFile(expectedClientId);
resetJSCache();
final String clientIdFromJS = getClientIdFromJS(); final String clientIdFromJS = getClientIdFromJS();
fAssertEquals("Health report client ID merged by JS", expectedClientId, clientIdFromJS); fAssertEquals("Health report client ID merged by JS", expectedClientId, clientIdFromJS);
final String clientIdFromJava = getClientIdFromJava(); final String clientIdFromJava = getClientIdFromJava();
@ -204,9 +219,20 @@ public class testUnifiedTelemetryClientId extends JavascriptBridgeTest {
return getBlockingFromJsString("clientId"); return getBlockingFromJsString("clientId");
} }
/**
* Must be called after Gecko is loaded.
*/
private void primeJsClientIdCache() {
// Not the cleanest way, but it works.
getClientIdFromJS();
}
/** /**
* Resets the client ID cache in ClientID.jsm. This method *must* be called after * Resets the client ID cache in ClientID.jsm. This method *must* be called after
* Gecko is loaded or else this method will hang. * Gecko is loaded or else this method will hang.
*
* Note: we do this for very specific reasons - see the comment in the test method
* ({@link #testUnifiedTelemetryClientId()}) for more.
*/ */
private void resetJSCache() { private void resetJSCache() {
// HACK: the backing JS method is a promise with no return value. Rather than writing a method // HACK: the backing JS method is a promise with no return value. Rather than writing a method

Просмотреть файл

@ -139,6 +139,7 @@ user_pref("security.turn_off_all_security_so_that_viruses_can_take_over_this_com
// use an additional pref here to allow automation to use the "normal" behavior. // use an additional pref here to allow automation to use the "normal" behavior.
user_pref("dom.use_xbl_scopes_for_remote_xul", true); user_pref("dom.use_xbl_scopes_for_remote_xul", true);
user_pref("captivedetect.canonicalURL", "http://%(server)s/captive-detect/success.txt");
// Get network events. // Get network events.
user_pref("network.activity.blipIntervalMilliseconds", 250); user_pref("network.activity.blipIntervalMilliseconds", 250);

Просмотреть файл

@ -88,6 +88,7 @@ ExtensionManagement.registerSchema("chrome://extensions/content/schemas/notifica
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/runtime.json"); ExtensionManagement.registerSchema("chrome://extensions/content/schemas/runtime.json");
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/storage.json"); ExtensionManagement.registerSchema("chrome://extensions/content/schemas/storage.json");
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/test.json"); ExtensionManagement.registerSchema("chrome://extensions/content/schemas/test.json");
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/events.json");
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/web_navigation.json"); ExtensionManagement.registerSchema("chrome://extensions/content/schemas/web_navigation.json");
ExtensionManagement.registerSchema("chrome://extensions/content/schemas/web_request.json"); ExtensionManagement.registerSchema("chrome://extensions/content/schemas/web_request.json");

Просмотреть файл

@ -6,7 +6,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement", XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
"resource://gre/modules/ExtensionManagement.jsm"); "resource://gre/modules/ExtensionManagement.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern", XPCOMUtils.defineLazyModuleGetter(this, "MatchURLFilters",
"resource://gre/modules/MatchPattern.jsm"); "resource://gre/modules/MatchPattern.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "WebNavigation", XPCOMUtils.defineLazyModuleGetter(this, "WebNavigation",
"resource://gre/modules/WebNavigation.jsm"); "resource://gre/modules/WebNavigation.jsm");
@ -99,7 +99,11 @@ function fillTransitionProperties(eventName, src, dst) {
// Similar to WebRequestEventManager but for WebNavigation. // Similar to WebRequestEventManager but for WebNavigation.
function WebNavigationEventManager(context, eventName) { function WebNavigationEventManager(context, eventName) {
let name = `webNavigation.${eventName}`; let name = `webNavigation.${eventName}`;
let register = callback => { let register = (callback, urlFilters) => {
// Don't create a MatchURLFilters instance if the listener does not include any filter.
let filters = urlFilters ?
new MatchURLFilters(urlFilters.url) : null;
let listener = data => { let listener = data => {
if (!data.browser) { if (!data.browser) {
return; return;
@ -129,7 +133,7 @@ function WebNavigationEventManager(context, eventName) {
runSafe(context, callback, data2); runSafe(context, callback, data2);
}; };
WebNavigation[eventName].addListener(listener); WebNavigation[eventName].addListener(listener, filters);
return () => { return () => {
WebNavigation[eventName].removeListener(listener); WebNavigation[eventName].removeListener(listener);
}; };

Просмотреть файл

@ -0,0 +1,322 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
[
{
"namespace": "events",
"description": "The <code>chrome.events</code> namespace contains common types used by APIs dispatching events to notify you when something interesting happens.",
"types": [
{
"id": "Rule",
"type": "object",
"description": "Description of a declarative rule for handling events.",
"properties": {
"id": {
"type": "string",
"optional": true,
"description": "Optional identifier that allows referencing this rule."
},
"tags": {
"type": "array",
"items": {"type": "string"},
"optional": true,
"description": "Tags can be used to annotate rules and perform operations on sets of rules."
},
"conditions": {
"type": "array",
"items": {"type": "any"},
"description": "List of conditions that can trigger the actions."
},
"actions": {
"type": "array",
"items": {"type": "any"},
"description": "List of actions that are triggered if one of the condtions is fulfilled."
},
"priority": {
"type": "integer",
"optional": true,
"description": "Optional priority of this rule. Defaults to 100."
}
}
},
{
"id": "Event",
"type": "object",
"description": "An object which allows the addition and removal of listeners for a Chrome event.",
"functions": [
{
"name": "addListener",
"type": "function",
"description": "Registers an event listener <em>callback</em> to an event.",
"parameters": [
{
"name": "callback",
"type": "function",
"description": "Called when an event occurs. The parameters of this function depend on the type of event."
}
]
},
{
"name": "removeListener",
"type": "function",
"description": "Deregisters an event listener <em>callback</em> from an event.",
"parameters": [
{
"name": "callback",
"type": "function",
"description": "Listener that shall be unregistered."
}
]
},
{
"name": "hasListener",
"type": "function",
"parameters": [
{
"name": "callback",
"type": "function",
"description": "Listener whose registration status shall be tested."
}
],
"returns": {
"type": "boolean",
"description": "True if <em>callback</em> is registered to the event."
}
},
{
"name": "hasListeners",
"type": "function",
"parameters": [],
"returns": {
"type": "boolean",
"description": "True if any event listeners are registered to the event."
}
},
{
"name": "addRules",
"unsupported": true,
"type": "function",
"description": "Registers rules to handle events.",
"parameters": [
{
"name": "eventName",
"type": "string",
"description": "Name of the event this function affects."
},
{
"name": "webViewInstanceId",
"type": "integer",
"description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call."
},
{
"name": "rules",
"type": "array",
"items": {"$ref": "Rule"},
"description": "Rules to be registered. These do not replace previously registered rules."
},
{
"name": "callback",
"optional": true,
"type": "function",
"parameters": [
{
"name": "rules",
"type": "array",
"items": {"$ref": "Rule"},
"description": "Rules that were registered, the optional parameters are filled with values."
}
],
"description": "Called with registered rules."
}
]
},
{
"name": "getRules",
"unsupported": true,
"type": "function",
"description": "Returns currently registered rules.",
"parameters": [
{
"name": "eventName",
"type": "string",
"description": "Name of the event this function affects."
},
{
"name": "webViewInstanceId",
"type": "integer",
"description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call."
},
{
"name": "ruleIdentifiers",
"optional": true,
"type": "array",
"items": {"type": "string"},
"description": "If an array is passed, only rules with identifiers contained in this array are returned."
},
{
"name": "callback",
"type": "function",
"parameters": [
{
"name": "rules",
"type": "array",
"items": {"$ref": "Rule"},
"description": "Rules that were registered, the optional parameters are filled with values."
}
],
"description": "Called with registered rules."
}
]
},
{
"name": "removeRules",
"unsupported": true,
"type": "function",
"description": "Unregisters currently registered rules.",
"parameters": [
{
"name": "eventName",
"type": "string",
"description": "Name of the event this function affects."
},
{
"name": "webViewInstanceId",
"type": "integer",
"description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call."
},
{
"name": "ruleIdentifiers",
"optional": true,
"type": "array",
"items": {"type": "string"},
"description": "If an array is passed, only rules with identifiers contained in this array are unregistered."
},
{
"name": "callback",
"optional": true,
"type": "function",
"parameters": [],
"description": "Called when rules were unregistered."
}
]
}
]
},
{
"id": "UrlFilter",
"type": "object",
"description": "Filters URLs for various criteria. See <a href='events#filtered'>event filtering</a>. All criteria are case sensitive.",
"properties": {
"hostContains": {
"type": "string",
"description": "Matches if the host name of the URL contains a specified string. To test whether a host name component has a prefix 'foo', use hostContains: '.foo'. This matches 'www.foobar.com' and 'foo.com', because an implicit dot is added at the beginning of the host name. Similarly, hostContains can be used to match against component suffix ('foo.') and to exactly match against components ('.foo.'). Suffix- and exact-matching for the last components need to be done separately using hostSuffix, because no implicit dot is added at the end of the host name.",
"optional": true
},
"hostEquals": {
"type": "string",
"description": "Matches if the host name of the URL is equal to a specified string.",
"optional": true
},
"hostPrefix": {
"type": "string",
"description": "Matches if the host name of the URL starts with a specified string.",
"optional": true
},
"hostSuffix": {
"type": "string",
"description": "Matches if the host name of the URL ends with a specified string.",
"optional": true
},
"pathContains": {
"type": "string",
"description": "Matches if the path segment of the URL contains a specified string.",
"optional": true
},
"pathEquals": {
"type": "string",
"description": "Matches if the path segment of the URL is equal to a specified string.",
"optional": true
},
"pathPrefix": {
"type": "string",
"description": "Matches if the path segment of the URL starts with a specified string.",
"optional": true
},
"pathSuffix": {
"type": "string",
"description": "Matches if the path segment of the URL ends with a specified string.",
"optional": true
},
"queryContains": {
"type": "string",
"description": "Matches if the query segment of the URL contains a specified string.",
"optional": true
},
"queryEquals": {
"type": "string",
"description": "Matches if the query segment of the URL is equal to a specified string.",
"optional": true
},
"queryPrefix": {
"type": "string",
"description": "Matches if the query segment of the URL starts with a specified string.",
"optional": true
},
"querySuffix": {
"type": "string",
"description": "Matches if the query segment of the URL ends with a specified string.",
"optional": true
},
"urlContains": {
"type": "string",
"description": "Matches if the URL (without fragment identifier) contains a specified string. Port numbers are stripped from the URL if they match the default port number.",
"optional": true
},
"urlEquals": {
"type": "string",
"description": "Matches if the URL (without fragment identifier) is equal to a specified string. Port numbers are stripped from the URL if they match the default port number.",
"optional": true
},
"urlMatches": {
"type": "string",
"description": "Matches if the URL (without fragment identifier) matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the <a href=\"https://github.com/google/re2/blob/master/doc/syntax.txt\">RE2 syntax</a>.",
"optional": true
},
"originAndPathMatches": {
"type": "string",
"description": "Matches if the URL without query segment and fragment identifier matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the <a href=\"https://github.com/google/re2/blob/master/doc/syntax.txt\">RE2 syntax</a>.",
"optional": true
},
"urlPrefix": {
"type": "string",
"description": "Matches if the URL (without fragment identifier) starts with a specified string. Port numbers are stripped from the URL if they match the default port number.",
"optional": true
},
"urlSuffix": {
"type": "string",
"description": "Matches if the URL (without fragment identifier) ends with a specified string. Port numbers are stripped from the URL if they match the default port number.",
"optional": true
},
"schemes": {
"type": "array",
"description": "Matches if the scheme of the URL is equal to any of the schemes specified in the array.",
"optional": true,
"items": { "type": "string" }
},
"ports": {
"type": "array",
"description": "Matches if the port of the URL is contained in any of the specified port lists. For example <code>[80, 443, [1000, 1200]]</code> matches all requests on port 80, 443 and in the range 1000-1200.",
"optional": true,
"items": {
"choices": [
{"type": "integer", "description": "A specific port."},
{"type": "array", "minItems": 2, "maxItems": 2, "items": {"type": "integer"}, "description": "A pair of integers identiying the start and end (both inclusive) of a port range."}
]
}
}
}
}
]
}
]

Просмотреть файл

@ -7,6 +7,7 @@ toolkit.jar:
content/extensions/schemas/alarms.json content/extensions/schemas/alarms.json
content/extensions/schemas/cookies.json content/extensions/schemas/cookies.json
content/extensions/schemas/downloads.json content/extensions/schemas/downloads.json
content/extensions/schemas/events.json
content/extensions/schemas/extension.json content/extensions/schemas/extension.json
content/extensions/schemas/extension_types.json content/extensions/schemas/extension_types.json
content/extensions/schemas/i18n.json content/extensions/schemas/i18n.json

Просмотреть файл

@ -31,6 +31,17 @@
"id": "TransitionQualifier", "id": "TransitionQualifier",
"type": "string", "type": "string",
"enum": ["client_redirect", "server_redirect", "forward_back", "from_address_bar"] "enum": ["client_redirect", "server_redirect", "forward_back", "from_address_bar"]
},
{
"id": "EventUrlFilters",
"type": "object",
"properties": {
"url": {
"type": "array",
"minItems": 1,
"items": { "$ref": "events.UrlFilter" }
}
}
} }
], ],
"functions": [ "functions": [
@ -140,14 +151,6 @@
"name": "onBeforeNavigate", "name": "onBeforeNavigate",
"type": "function", "type": "function",
"description": "Fired when a navigation is about to occur.", "description": "Fired when a navigation is about to occur.",
"filters": [
{
"name": "url",
"type": "array",
"items": { "$ref": "events.UrlFilter" },
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
],
"parameters": [ "parameters": [
{ {
"type": "object", "type": "object",
@ -161,20 +164,20 @@
"timeStamp": {"type": "number", "description": "The time when the browser was about to start the navigation, in milliseconds since the epoch."} "timeStamp": {"type": "number", "description": "The time when the browser was about to start the navigation, in milliseconds since the epoch."}
} }
} }
],
"extraParameters": [
{
"name": "filters",
"optional": true,
"$ref": "EventUrlFilters",
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
] ]
}, },
{ {
"name": "onCommitted", "name": "onCommitted",
"type": "function", "type": "function",
"description": "Fired when a navigation is committed. The document (and the resources it refers to, such as images and subframes) might still be downloading, but at least part of the document has been received from the server and the browser has decided to switch to the new document.", "description": "Fired when a navigation is committed. The document (and the resources it refers to, such as images and subframes) might still be downloading, but at least part of the document has been received from the server and the browser has decided to switch to the new document.",
"filters": [
{
"name": "url",
"type": "array",
"items": { "$ref": "events.UrlFilter" },
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
],
"parameters": [ "parameters": [
{ {
"type": "object", "type": "object",
@ -189,20 +192,20 @@
"timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."} "timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."}
} }
} }
],
"extraParameters": [
{
"name": "filters",
"optional": true,
"$ref": "EventUrlFilters",
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
] ]
}, },
{ {
"name": "onDOMContentLoaded", "name": "onDOMContentLoaded",
"type": "function", "type": "function",
"description": "Fired when the page's DOM is fully constructed, but the referenced resources may not finish loading.", "description": "Fired when the page's DOM is fully constructed, but the referenced resources may not finish loading.",
"filters": [
{
"name": "url",
"type": "array",
"items": { "$ref": "events.UrlFilter" },
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
],
"parameters": [ "parameters": [
{ {
"type": "object", "type": "object",
@ -215,20 +218,20 @@
"timeStamp": {"type": "number", "description": "The time when the page's DOM was fully constructed, in milliseconds since the epoch."} "timeStamp": {"type": "number", "description": "The time when the page's DOM was fully constructed, in milliseconds since the epoch."}
} }
} }
],
"extraParameters": [
{
"name": "filters",
"optional": true,
"$ref": "EventUrlFilters",
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
] ]
}, },
{ {
"name": "onCompleted", "name": "onCompleted",
"type": "function", "type": "function",
"description": "Fired when a document, including the resources it refers to, is completely loaded and initialized.", "description": "Fired when a document, including the resources it refers to, is completely loaded and initialized.",
"filters": [
{
"name": "url",
"type": "array",
"items": { "$ref": "events.UrlFilter" },
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
],
"parameters": [ "parameters": [
{ {
"type": "object", "type": "object",
@ -241,20 +244,20 @@
"timeStamp": {"type": "number", "description": "The time when the document finished loading, in milliseconds since the epoch."} "timeStamp": {"type": "number", "description": "The time when the document finished loading, in milliseconds since the epoch."}
} }
} }
],
"extraParameters": [
{
"name": "filters",
"optional": true,
"$ref": "EventUrlFilters",
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
] ]
}, },
{ {
"name": "onErrorOccurred", "name": "onErrorOccurred",
"type": "function", "type": "function",
"description": "Fired when an error occurs and the navigation is aborted. This can happen if either a network error occurred, or the user aborted the navigation.", "description": "Fired when an error occurs and the navigation is aborted. This can happen if either a network error occurred, or the user aborted the navigation.",
"filters": [
{
"name": "url",
"type": "array",
"items": { "$ref": "events.UrlFilter" },
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
],
"parameters": [ "parameters": [
{ {
"type": "object", "type": "object",
@ -268,6 +271,14 @@
"timeStamp": {"type": "number", "description": "The time when the error occurred, in milliseconds since the epoch."} "timeStamp": {"type": "number", "description": "The time when the error occurred, in milliseconds since the epoch."}
} }
} }
],
"extraParameters": [
{
"name": "filters",
"optional": true,
"$ref": "EventUrlFilters",
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
] ]
}, },
{ {
@ -275,14 +286,6 @@
"unsupported": true, "unsupported": true,
"type": "function", "type": "function",
"description": "Fired when a new window, or a new tab in an existing window, is created to host a navigation.", "description": "Fired when a new window, or a new tab in an existing window, is created to host a navigation.",
"filters": [
{
"name": "url",
"type": "array",
"items": { "$ref": "events.UrlFilter" },
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
],
"parameters": [ "parameters": [
{ {
"type": "object", "type": "object",
@ -296,20 +299,20 @@
"timeStamp": {"type": "number", "description": "The time when the browser was about to create a new view, in milliseconds since the epoch."} "timeStamp": {"type": "number", "description": "The time when the browser was about to create a new view, in milliseconds since the epoch."}
} }
} }
],
"extraParameters": [
{
"name": "filters",
"optional": true,
"$ref": "EventUrlFilters",
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
] ]
}, },
{ {
"name": "onReferenceFragmentUpdated", "name": "onReferenceFragmentUpdated",
"type": "function", "type": "function",
"description": "Fired when the reference fragment of a frame was updated. All future events for that frame will use the updated URL.", "description": "Fired when the reference fragment of a frame was updated. All future events for that frame will use the updated URL.",
"filters": [
{
"name": "url",
"type": "array",
"items": { "$ref": "events.UrlFilter" },
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
],
"parameters": [ "parameters": [
{ {
"type": "object", "type": "object",
@ -324,6 +327,14 @@
"timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."} "timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."}
} }
} }
],
"extraParameters": [
{
"name": "filters",
"optional": true,
"$ref": "EventUrlFilters",
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
] ]
}, },
{ {
@ -347,14 +358,6 @@
"name": "onHistoryStateUpdated", "name": "onHistoryStateUpdated",
"type": "function", "type": "function",
"description": "Fired when the frame's history was updated to a new URL. All future events for that frame will use the updated URL.", "description": "Fired when the frame's history was updated to a new URL. All future events for that frame will use the updated URL.",
"filters": [
{
"name": "url",
"type": "array",
"items": { "$ref": "events.UrlFilter" },
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
],
"parameters": [ "parameters": [
{ {
"type": "object", "type": "object",
@ -369,6 +372,14 @@
"timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."} "timeStamp": {"type": "number", "description": "The time when the navigation was committed, in milliseconds since the epoch."}
} }
} }
],
"extraParameters": [
{
"name": "filters",
"optional": true,
"$ref": "EventUrlFilters",
"description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
}
] ]
} }
] ]

Просмотреть файл

@ -93,5 +93,7 @@ skip-if = (os == 'android') # Bug 1258975 on android.
skip-if = (os == 'android' || buildapp == 'b2g') # webrequest api uninplemented (bug 1199504). Bug 1258975 on android. skip-if = (os == 'android' || buildapp == 'b2g') # webrequest api uninplemented (bug 1199504). Bug 1258975 on android.
[test_ext_webnavigation.html] [test_ext_webnavigation.html]
skip-if = (os == 'android' || buildapp == 'b2g') # needs TabManager which is not yet implemented. Bug 1258975 on android. skip-if = (os == 'android' || buildapp == 'b2g') # needs TabManager which is not yet implemented. Bug 1258975 on android.
[test_ext_webnavigation_filters.html]
skip-if = (os == 'android' || buildapp == 'b2g') # needs TabManager which is not yet implemented. Bug 1258975 on android.
[test_ext_subframes_privileges.html] [test_ext_subframes_privileges.html]
skip-if = (os == 'android' || buildapp == 'b2g') # neews TabManager which is not yet implemented. Bug 1258975 on android. skip-if = (os == 'android' || buildapp == 'b2g') # neews TabManager which is not yet implemented. Bug 1258975 on android.

Просмотреть файл

@ -0,0 +1,303 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for simple WebExtension</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script type="text/javascript" src="head.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="text/javascript">
"use strict";
add_task(function* test_webnav_unresolved_uri_on_expected_URI_scheme() {
function backgroundScript() {
let lastTest;
function cleanupTestListeners() {
if (lastTest) {
let {event, okListener, failListener} = lastTest;
lastTest = null;
browser.test.log(`Cleanup previous test event listeners`);
browser.webNavigation[event].removeListener(okListener);
browser.webNavigation[event].removeListener(failListener);
}
}
function createTestListener(event, fail, urlFilter) {
function listener(details) {
let log = JSON.stringify({url: details.url, urlFilter});
if (fail) {
browser.test.fail(`Got an unexpected ${event} on the failure listener: ${log}`);
} else {
browser.test.succeed(`Got the expected ${event} on the success listener: ${log}`);
}
cleanupTestListeners();
browser.test.sendMessage("test-filter-next");
}
browser.webNavigation[event].addListener(listener, urlFilter);
return listener;
}
browser.test.onMessage.addListener((msg, event, okFilter, failFilter) => {
if (msg !== "test-filter") {
return;
}
lastTest = {
event,
// Register the failListener first, which should not be called
// and if it is called the test scenario is marked as a failure.
failListener: createTestListener(event, true, failFilter),
okListener: createTestListener(event, false, okFilter),
};
browser.test.sendMessage("test-filter-ready");
});
browser.test.sendMessage("ready");
}
let extensionData = {
manifest: {
permissions: [
"webNavigation",
],
},
background: "new " + backgroundScript,
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
yield extension.startup();
yield extension.awaitMessage("ready");
let win = window.open();
let testFilterScenarios = [
{
url: "http://example.net/browser",
filters: [
// schemes
{
okFilter: [{schemes: ["http"]}],
failFilter: [{schemes: ["https"]}],
},
// ports
{
okFilter: [{ports: [80, 22, 443]}],
failFilter: [{ports: [81, 82, 83]}],
},
{
okFilter: [{ports: [22, 443, [10, 80]]}],
failFilter: [{ports: [22, 23, [81, 100]]}],
},
],
},
{
url: "http://example.net/browser?param=1#ref",
filters: [
// host: Equals, Contains, Prefix, Suffix
{
okFilter: [{hostEquals: "example.net"}],
failFilter: [{hostEquals: "example.com"}],
},
{
okFilter: [{hostContains: ".example"}],
failFilter: [{hostContains: ".www"}],
},
{
okFilter: [{hostPrefix: "example"}],
failFilter: [{hostPrefix: "www"}],
},
{
okFilter: [{hostSuffix: "net"}],
failFilter: [{hostSuffix: "com"}],
},
// path: Equals, Contains, Prefix, Suffix
{
okFilter: [{pathEquals: "/browser"}],
failFilter: [{pathEquals: "/"}],
},
{
okFilter: [{pathContains: "brow"}],
failFilter: [{pathContains: "tool"}],
},
{
okFilter: [{pathPrefix: "/bro"}],
failFilter: [{pathPrefix: "/tool"}],
},
{
okFilter: [{pathSuffix: "wser"}],
failFilter: [{pathSuffix: "kit"}],
},
// query: Equals, Contains, Prefix, Suffix
{
okFilter: [{queryEquals: "param=1"}],
failFilter: [{queryEquals: "wrongparam=2"}],
},
{
okFilter: [{queryContains: "param"}],
failFilter: [{queryContains: "wrongparam"}],
},
{
okFilter: [{queryPrefix: "param="}],
failFilter: [{queryPrefix: "wrong"}],
},
{
okFilter: [{querySuffix: "=1"}],
failFilter: [{querySuffix: "=2"}],
},
// urlMatches, originAndPathMatches
{
okFilter: [{urlMatches: "example.net/.*\?param=1"}],
failFilter: [{urlMatches: "example.net/.*\?wrongparam=2"}],
},
{
okFilter: [{originAndPathMatches: "example.net\/browser"}],
failFilter: [{originAndPathMatches: "example.net/.*\?param=1"}],
},
],
},
{
url: "http://example.net/browser",
filters: [
// multiple criteria in a single filter:
// if one of the critera is not verified, the event should not be received.
{
okFilter: [{schemes: ["http"], ports: [80, 22, 443]}],
failFilter: [{schemes: ["http"], ports: [81, 82, 83]}],
},
// multiple urlFilters on the same listener
// if at least one of the critera is verified, the event should be received.
{
okFilter: [{schemes: ["https"]}, {ports: [80, 22, 443]}],
failFilter: [{schemes: ["https"]}, {ports: [81, 82, 83]}],
},
],
},
];
function* runTestScenario(event, {url, filters}) {
for (let testFilters of filters) {
let {okFilter, failFilter} = testFilters;
info(`Prepare the new test scenario: ${event} ${url} ${JSON.stringify(testFilters)}`);
win.location = "about:blank";
extension.sendMessage("test-filter", event, {url: okFilter}, {url: failFilter});
yield extension.awaitMessage("test-filter-ready");
info(`Loading the test url: ${url}`);
win.location = url;
yield extension.awaitMessage("test-filter-next");
info("Test scenario completed. Moving to the next test scenario.");
}
}
const BASE_WEBNAV_EVENTS = [
"onBeforeNavigate",
"onCommitted",
"onDOMContentLoaded",
"onCompleted",
];
info("WebNavigation event filters test scenarios starting...");
for (let filterScenario of testFilterScenarios) {
for (let event of BASE_WEBNAV_EVENTS) {
yield runTestScenario(event, filterScenario);
}
}
info("WebNavigation event filters test onReferenceFragmentUpdated scenario starting...");
const BASE = "http://mochi.test:8888/tests/toolkit/components/extensions/test/mochitest";
let url = BASE + "/file_WebNavigation_page3.html";
let okFilter = [{urlContains: "_page3.html"}];
let failFilter = [{ports: [444]}];
let event = "onCompleted";
info(`Loading the initial test url: ${url}`);
extension.sendMessage("test-filter", event, {url: okFilter}, {url: failFilter});
yield extension.awaitMessage("test-filter-ready");
win.location = url;
yield extension.awaitMessage("test-filter-next");
event = "onReferenceFragmentUpdated";
extension.sendMessage("test-filter", event, {url: okFilter}, {url: failFilter});
yield extension.awaitMessage("test-filter-ready");
win.location = url + "#ref1";
yield extension.awaitMessage("test-filter-next");
info("WebNavigation event filters test onHistoryStateUpdated scenario starting...");
event = "onHistoryStateUpdated";
extension.sendMessage("test-filter", event, {url: okFilter}, {url: failFilter});
yield extension.awaitMessage("test-filter-ready");
win.history.pushState({}, "", BASE + "/pushState_page3.html");
yield extension.awaitMessage("test-filter-next");
// TODO: add additional specific tests for the other webNavigation events:
// onErrorOccurred (and onCreatedNavigationTarget on supported)
info("WebNavigation event filters test scenarios completed.");
yield extension.unload();
win.close();
});
add_task(function* test_webnav_empty_filter_validation_error() {
function backgroundScript() {
let catchedException;
try {
browser.webNavigation.onCompleted.addListener(
// Empty callback (not really used)
() => {},
// Empty filter (which should raise a validation error exception).
{url: []}
);
} catch (e) {
catchedException = e;
browser.test.log(`Got an exception`);
}
if (catchedException &&
catchedException.message.includes("Type error for parameter filters") &&
catchedException.message.includes("Array requires at least 1 items; you have 0")) {
browser.test.notifyPass("webNav.emptyFilterValidationError");
} else {
browser.test.notifyFail("webNav.emptyFilterValidationError");
}
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: [
"webNavigation",
],
},
background: "new " + backgroundScript,
});
yield extension.startup();
yield extension.awaitFinish("webNav.emptyFilterValidationError");
yield extension.unload();
});
</script>
</body>
</html>

Просмотреть файл

@ -470,7 +470,7 @@ var LoginManagerContent = {
*/ */
fillForm({ topDocument, loginFormOrigin, loginsFound, recipes, inputElement }) { fillForm({ topDocument, loginFormOrigin, loginsFound, recipes, inputElement }) {
let topState = this.stateForDocument(topDocument); let topState = this.stateForDocument(topDocument);
if (!topState.loginFormForFill) { if (!inputElement && !topState.loginFormForFill) {
log("fillForm: There is no login form anymore. The form may have been", log("fillForm: There is no login form anymore. The form may have been",
"removed or the document may have changed."); "removed or the document may have changed.");
return; return;

Просмотреть файл

@ -3,6 +3,7 @@ support-files =
../formsubmit.sjs ../formsubmit.sjs
authenticate.sjs authenticate.sjs
form_basic.html form_basic.html
form_basic_iframe.html
formless_basic.html formless_basic.html
form_same_origin_action.html form_same_origin_action.html
form_cross_origin_insecure_action.html form_cross_origin_insecure_action.html
@ -46,7 +47,8 @@ support-files =
[browser_passwordmgr_editing.js] [browser_passwordmgr_editing.js]
skip-if = os == "linux" skip-if = os == "linux"
[browser_context_menu.js] [browser_context_menu.js]
subsuite = clipboard skip-if = e10s
[browser_context_menu_iframe.js]
skip-if = e10s skip-if = e10s
[browser_passwordmgr_contextmenu.js] [browser_passwordmgr_contextmenu.js]
subsuite = clipboard subsuite = clipboard

Просмотреть файл

@ -229,65 +229,6 @@ add_task(function* test_context_menu_username_login_fill() {
}); });
}); });
/**
* Check if the password field is correctly filled when it's in an iframe.
*/
add_task(function* test_context_menu_iframe_fill() {
Services.prefs.setBoolPref("signon.schemeUpgrades", true);
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
}, function* (browser) {
let iframe = browser.contentWindow.document.getElementById("test-iframe");
let passwordInput = iframe.contentDocument.getElementById("form-basic-password");
let contextMenuShownPromise = BrowserTestUtils.waitForEvent(window, "popupshown");
let eventDetails = {type: "contextmenu", button: 2};
// To click at the right point we have to take into account the iframe offset.
let iframeRect = iframe.getBoundingClientRect();
let inputRect = passwordInput.getBoundingClientRect();
let clickPos = {
offsetX: iframeRect.left + inputRect.width / 2,
offsetY: iframeRect.top + inputRect.height / 2,
};
// Synthesize a right mouse click over the password input element.
BrowserTestUtils.synthesizeMouse(passwordInput, clickPos.offsetX, clickPos.offsetY, eventDetails, browser);
yield contextMenuShownPromise;
// Synthesize a mouse click over the fill login menu header.
let popupHeader = document.getElementById("fill-login");
let popupShownPromise = BrowserTestUtils.waitForEvent(popupHeader, "popupshown");
EventUtils.synthesizeMouseAtCenter(popupHeader, {});
yield popupShownPromise;
let popupMenu = document.getElementById("fill-login-popup");
// Stores the original value of username
let usernameInput = iframe.contentDocument.getElementById("form-basic-username");
let usernameOriginalValue = usernameInput.value;
// Execute the command of the first login menuitem found at the context menu.
let firstLoginItem = popupMenu.getElementsByClassName("context-login-item")[0];
firstLoginItem.doCommand();
yield BrowserTestUtils.waitForEvent(passwordInput, "input", "Password input value changed");
// Find the used login by it's username.
let login = getLoginFromUsername(firstLoginItem.label);
Assert.equal(login.password, passwordInput.value, "Password filled and correct.");
Assert.equal(usernameOriginalValue,
usernameInput.value,
"Username value was not changed.");
let contextMenu = document.getElementById("contentAreaContextMenu");
contextMenu.hidePopup();
});
});
/** /**
* Synthesize mouse clicks to open the password manager context menu popup * Synthesize mouse clicks to open the password manager context menu popup
* for a target password input element. * for a target password input element.

Просмотреть файл

@ -0,0 +1,136 @@
/*
* Test the password manager context menu.
*/
"use strict";
const TEST_HOSTNAME = "https://example.com";
// Test with a page that only has a form within an iframe, not in the top-level document
const IFRAME_PAGE_PATH = "/browser/toolkit/components/passwordmgr/test/browser/form_basic_iframe.html";
/**
* Initialize logins needed for the tests and disable autofill
* for login forms for easier testing of manual fill.
*/
add_task(function* test_initialize() {
Services.prefs.setBoolPref("signon.autofillForms", false);
registerCleanupFunction(() => {
Services.prefs.clearUserPref("signon.autofillForms");
Services.prefs.clearUserPref("signon.schemeUpgrades");
});
for (let login of loginList()) {
Services.logins.addLogin(login);
}
});
/**
* Check if the password field is correctly filled when it's in an iframe.
*/
add_task(function* test_context_menu_iframe_fill() {
Services.prefs.setBoolPref("signon.schemeUpgrades", true);
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + IFRAME_PAGE_PATH
}, function* (browser) {
let iframe = browser.contentWindow.document.getElementById("test-iframe");
let passwordInput = iframe.contentDocument.getElementById("form-basic-password");
let contextMenuShownPromise = BrowserTestUtils.waitForEvent(window, "popupshown");
let eventDetails = {type: "contextmenu", button: 2};
// To click at the right point we have to take into account the iframe offset.
let iframeRect = iframe.getBoundingClientRect();
let inputRect = passwordInput.getBoundingClientRect();
let clickPos = {
offsetX: iframeRect.left + inputRect.width / 2,
offsetY: iframeRect.top + inputRect.height / 2,
};
// Synthesize a right mouse click over the password input element.
BrowserTestUtils.synthesizeMouse(passwordInput, clickPos.offsetX, clickPos.offsetY, eventDetails, browser);
yield contextMenuShownPromise;
// Synthesize a mouse click over the fill login menu header.
let popupHeader = document.getElementById("fill-login");
let popupShownPromise = BrowserTestUtils.waitForEvent(popupHeader, "popupshown");
EventUtils.synthesizeMouseAtCenter(popupHeader, {});
yield popupShownPromise;
let popupMenu = document.getElementById("fill-login-popup");
// Stores the original value of username
let usernameInput = iframe.contentDocument.getElementById("form-basic-username");
let usernameOriginalValue = usernameInput.value;
// Execute the command of the first login menuitem found at the context menu.
let firstLoginItem = popupMenu.getElementsByClassName("context-login-item")[0];
firstLoginItem.doCommand();
yield BrowserTestUtils.waitForEvent(passwordInput, "input", "Password input value changed");
// Find the used login by it's username.
let login = getLoginFromUsername(firstLoginItem.label);
Assert.equal(login.password, passwordInput.value, "Password filled and correct.");
Assert.equal(usernameOriginalValue,
usernameInput.value,
"Username value was not changed.");
let contextMenu = document.getElementById("contentAreaContextMenu");
contextMenu.hidePopup();
});
});
/**
* Search for a login by it's username.
*
* Only unique login/hostname combinations should be used at this test.
*/
function getLoginFromUsername(username) {
return loginList().find(login => login.username == username);
}
/**
* List of logins used for the test.
*
* We should only use unique usernames in this test,
* because we need to search logins by username. There is one duplicate u+p combo
* in order to test de-duping in the menu.
*/
function loginList() {
return [
LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: "username",
password: "password",
}),
// Same as above but HTTP in order to test de-duping.
LoginTestUtils.testData.formLogin({
hostname: "http://example.com",
formSubmitURL: "http://example.com",
username: "username",
password: "password",
}),
LoginTestUtils.testData.formLogin({
hostname: "http://example.com",
formSubmitURL: "http://example.com",
username: "username1",
password: "password1",
}),
LoginTestUtils.testData.formLogin({
hostname: "https://example.com",
formSubmitURL: "https://example.com",
username: "username2",
password: "password2",
}),
LoginTestUtils.testData.formLogin({
hostname: "http://example.org",
formSubmitURL: "http://example.org",
username: "username-cross-origin",
password: "password-cross-origin",
}),
];
}

Просмотреть файл

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<!-- Form in an iframe -->
<iframe src="https://example.org/browser/toolkit/components/passwordmgr/test/browser/form_basic.html" id="test-iframe"></iframe>
</body>
</html>

Просмотреть файл

@ -197,6 +197,9 @@ var PrintUtils = {
this._sourceBrowser = aListenerObj.getSourceBrowser(); this._sourceBrowser = aListenerObj.getSourceBrowser();
this._originalTitle = this._sourceBrowser.contentTitle; this._originalTitle = this._sourceBrowser.contentTitle;
this._originalURL = this._sourceBrowser.currentURI.spec; this._originalURL = this._sourceBrowser.currentURI.spec;
// Here we log telemetry data for when the user enters print preview.
this.logTelemetry("PRINT_PREVIEW_OPENED_COUNT");
} else { } else {
// collapse the browser here -- it will be shown in // collapse the browser here -- it will be shown in
// enterPrintPreview; this forces a reflow which fixes display // enterPrintPreview; this forces a reflow which fixes display
@ -530,6 +533,9 @@ var PrintUtils = {
URL: this._listener.getSourceBrowser().currentURI.spec, URL: this._listener.getSourceBrowser().currentURI.spec,
windowID: this._listener.getSourceBrowser().outerWindowID, windowID: this._listener.getSourceBrowser().outerWindowID,
}); });
// Here we log telemetry data for when the user enters simplify mode.
this.logTelemetry("PRINT_PREVIEW_SIMPLIFY_PAGE_OPENED_COUNT");
} }
} else { } else {
sendEnterPreviewMessage(this._listener.getSourceBrowser(), false); sendEnterPreviewMessage(this._listener.getSourceBrowser(), false);
@ -635,6 +641,12 @@ var PrintUtils = {
this._listener.onExit(); this._listener.onExit();
}, },
logTelemetry: function (ID)
{
let histogram = Services.telemetry.getHistogramById(ID);
histogram.add(true);
},
onKeyDownPP: function (aEvent) onKeyDownPP: function (aEvent)
{ {
// Esc exits the PP // Esc exits the PP

Просмотреть файл

@ -5879,6 +5879,22 @@
"n_buckets": 50, "n_buckets": 50,
"description": "(Bug 1207089) Time in ms between displaying a popup notification and dismissing it without an action the first time, keyed by ID" "description": "(Bug 1207089) Time in ms between displaying a popup notification and dismissing it without an action the first time, keyed by ID"
}, },
"PRINT_PREVIEW_OPENED_COUNT": {
"alert_emails": ["carnold@mozilla.org"],
"bug_numbers": [1275570],
"expires_in_version": "56",
"kind": "count",
"releaseChannelCollection": "opt-out",
"description": "A counter incremented every time the browser enters print preview."
},
"PRINT_PREVIEW_SIMPLIFY_PAGE_OPENED_COUNT": {
"alert_emails": ["carnold@mozilla.org"],
"bug_numbers": [1275570],
"expires_in_version": "56",
"kind": "count",
"releaseChannelCollection": "opt-out",
"description": "A counter incremented every time the browser enters simplified mode on print preview."
},
"DEVTOOLS_DEBUGGER_RDP_LOCAL_RELOAD_MS": { "DEVTOOLS_DEBUGGER_RDP_LOCAL_RELOAD_MS": {
"expires_in_version": "never", "expires_in_version": "never",
"kind": "exponential", "kind": "exponential",

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше