mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-12 21:05:36 +00:00
Merge fx-team to m-c a=merge
This commit is contained in:
commit
e43a05d6d2
@ -143,6 +143,11 @@ function createBookmark (data) {
|
||||
}
|
||||
exports.createBookmark = createBookmark;
|
||||
|
||||
function historyBatch () {
|
||||
hsrv.runInBatchMode(() => {}, null);
|
||||
}
|
||||
exports.historyBatch = historyBatch;
|
||||
|
||||
function createBookmarkItem (data) {
|
||||
let deferred = defer();
|
||||
data = data || {};
|
||||
|
@ -29,7 +29,7 @@ const { search } = require('sdk/places/history');
|
||||
const {
|
||||
invalidResolve, invalidReject, createTree, createBookmark,
|
||||
compareWithHost, addVisits, resetPlaces, createBookmarkItem,
|
||||
removeVisits
|
||||
removeVisits, historyBatch
|
||||
} = require('./places-helper');
|
||||
const { save, MENU, UNSORTED } = require('sdk/places/bookmarks');
|
||||
const { promisedEmitter } = require('sdk/places/utils');
|
||||
@ -236,9 +236,8 @@ exports['test history-start-batch, history-end-batch, history-start-clear'] = fu
|
||||
on(startEvent, 'data', startHandler);
|
||||
on(clearEvent, 'data', clearHandler);
|
||||
|
||||
createBookmark().then(() => {
|
||||
resetPlaces(complete);
|
||||
})
|
||||
historyBatch();
|
||||
resetPlaces(complete);
|
||||
};
|
||||
|
||||
exports['test history-visit, history-title-changed'] = function (assert, done) {
|
||||
|
@ -1861,7 +1861,7 @@ pref("browser.polaris.enabled", false);
|
||||
pref("privacy.trackingprotection.ui.enabled", false);
|
||||
#endif
|
||||
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
#ifdef NIGHTLY_BUILD
|
||||
// At the moment, autostart.2 is used, while autostart.1 is unused.
|
||||
// We leave it here set to false to reset users' defaults and allow
|
||||
// us to change everybody to true in the future, when desired.
|
||||
|
@ -164,6 +164,9 @@ const gXPInstallObserver = {
|
||||
this._removeProgressNotification(browser);
|
||||
break; }
|
||||
case "addon-install-confirmation": {
|
||||
let unsigned = installInfo.installs.filter(i => i.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING);
|
||||
let someUnsigned = unsigned.length > 0 && unsigned.length < installInfo.installs.length;
|
||||
|
||||
options.eventCallback = (aEvent) => {
|
||||
switch (aEvent) {
|
||||
case "removed":
|
||||
@ -179,10 +182,21 @@ const gXPInstallObserver = {
|
||||
addonList.firstChild.remove();
|
||||
|
||||
for (let install of installInfo.installs) {
|
||||
let container = document.createElement("hbox");
|
||||
|
||||
let name = document.createElement("label");
|
||||
name.setAttribute("value", install.addon.name);
|
||||
name.setAttribute("class", "addon-install-confirmation-name");
|
||||
addonList.appendChild(name);
|
||||
container.appendChild(name);
|
||||
|
||||
if (someUnsigned && install.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
|
||||
let unsigned = document.createElement("label");
|
||||
unsigned.setAttribute("value", gNavigatorBundle.getString("addonInstall.unsigned"));
|
||||
unsigned.setAttribute("class", "addon-install-confirmation-unsigned");
|
||||
container.appendChild(unsigned);
|
||||
}
|
||||
|
||||
addonList.appendChild(container);
|
||||
}
|
||||
|
||||
this.acceptInstallation = () => {
|
||||
@ -201,7 +215,20 @@ const gXPInstallObserver = {
|
||||
options.learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") +
|
||||
"find-and-install-add-ons";
|
||||
|
||||
messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
|
||||
if (unsigned.length == installInfo.installs.length) {
|
||||
// None of the add-ons are verified
|
||||
messageString = gNavigatorBundle.getString("addonConfirmInstallUnsigned.message");
|
||||
}
|
||||
else if (unsigned.length == 0) {
|
||||
// All add-ons are verified or don't need to be verified
|
||||
messageString = gNavigatorBundle.getString("addonConfirmInstall.message");
|
||||
}
|
||||
else {
|
||||
// Some of the add-ons are unverified, the list of names will indicate
|
||||
// which
|
||||
messageString = gNavigatorBundle.getString("addonConfirmInstallSomeUnsigned.message");
|
||||
}
|
||||
|
||||
messageString = PluralForm.get(installInfo.installs.length, messageString);
|
||||
messageString = messageString.replace("#1", brandShortName);
|
||||
messageString = messageString.replace("#2", installInfo.installs.length);
|
||||
|
@ -773,6 +773,14 @@ window[chromehidden~="toolbar"] toolbar:not(#nav-bar):not(#TabsToolbar):not(#pri
|
||||
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#click-to-play-plugins-notification");
|
||||
}
|
||||
|
||||
#password-fill-notification {
|
||||
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#password-fill-notification");
|
||||
}
|
||||
|
||||
.login-fill-item {
|
||||
-moz-binding: url("chrome://passwordmgr/content/login.xml#login");
|
||||
}
|
||||
|
||||
.plugin-popupnotification-centeritem {
|
||||
-moz-binding: url("chrome://browser/content/urlbarBindings.xml#plugin-popupnotification-center-item");
|
||||
}
|
||||
|
@ -62,6 +62,13 @@
|
||||
</popupnotificationcontent>
|
||||
</popupnotification>
|
||||
|
||||
<vbox id="login-fill-doorhanger" hidden="true">
|
||||
<description id="login-fill-testing"
|
||||
value="Thanks for testing the login fill doorhanger!"/>
|
||||
<textbox id="login-fill-filter"/>
|
||||
<richlistbox id="login-fill-list"/>
|
||||
</vbox>
|
||||
|
||||
#ifdef E10S_TESTING_ONLY
|
||||
<popupnotification id="enable-e10s-notification" hidden="true">
|
||||
<popupnotificationcontent orient="vertical"/>
|
||||
|
@ -455,6 +455,110 @@ function test_multiple() {
|
||||
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
|
||||
},
|
||||
|
||||
function test_someunverified() {
|
||||
// This test is only relevant if using the new doorhanger UI and allowing
|
||||
// unsigned add-ons
|
||||
if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
|
||||
Preferences.get("xpinstall.signatures.required", true)) {
|
||||
runNextTest();
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for the progress notification
|
||||
wait_for_progress_notification(function(aPanel) {
|
||||
// Wait for the install confirmation dialog
|
||||
wait_for_install_dialog(function() {
|
||||
let notification = document.getElementById("addon-install-confirmation-notification");
|
||||
let message = notification.getAttribute("label");
|
||||
is(message, "Caution: This site would like to install 2 add-ons in " + gApp + ", some of which are unverified. Proceed at your own risk.",
|
||||
"Should see the right message");
|
||||
|
||||
let container = document.getElementById("addon-install-confirmation-content");
|
||||
is(container.childNodes.length, 2, "Should be two items listed");
|
||||
is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
|
||||
is(container.childNodes[0].lastChild.getAttribute("class"),
|
||||
"addon-install-confirmation-unsigned", "Should have the unverified marker");
|
||||
is(container.childNodes[1].firstChild.getAttribute("value"), "Theme Test", "Should have the right add-on");
|
||||
is(container.childNodes[1].childNodes.length, 1, "Shouldn't have the unverified marker");
|
||||
|
||||
// Wait for the complete notification
|
||||
wait_for_notification("addon-install-complete", function(aPanel) {
|
||||
AddonManager.getAddonsByIDs(["restartless-xpi@tests.mozilla.org",
|
||||
"theme-xpi@tests.mozilla.org"], function([a, t]) {
|
||||
a.uninstall();
|
||||
// Installing a new theme tries to switch to it, switch back to the
|
||||
// default theme.
|
||||
t.userDisabled = true;
|
||||
t.uninstall();
|
||||
|
||||
Services.perms.remove("example.com", "install");
|
||||
wait_for_notification_close(runNextTest);
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
});
|
||||
|
||||
accept_install_dialog();
|
||||
});
|
||||
});
|
||||
|
||||
var pm = Services.perms;
|
||||
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
|
||||
|
||||
var triggers = encodeURIComponent(JSON.stringify({
|
||||
"Extension XPI": "restartless.xpi",
|
||||
"Theme XPI": "theme.xpi"
|
||||
}));
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
|
||||
},
|
||||
|
||||
function test_allunverified() {
|
||||
// This test is only relevant if using the new doorhanger UI and allowing
|
||||
// unsigned add-ons
|
||||
if (!Preferences.get("xpinstall.customConfirmationUI", false) ||
|
||||
Preferences.get("xpinstall.signatures.required", true)) {
|
||||
runNextTest();
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for the progress notification
|
||||
wait_for_progress_notification(function(aPanel) {
|
||||
// Wait for the install confirmation dialog
|
||||
wait_for_install_dialog(function() {
|
||||
let notification = document.getElementById("addon-install-confirmation-notification");
|
||||
let message = notification.getAttribute("label");
|
||||
is(message, "Caution: This site would like to install an unverified add-on in " + gApp + ". Proceed at your own risk.");
|
||||
|
||||
let container = document.getElementById("addon-install-confirmation-content");
|
||||
is(container.childNodes.length, 1, "Should be one item listed");
|
||||
is(container.childNodes[0].firstChild.getAttribute("value"), "XPI Test", "Should have the right add-on");
|
||||
is(container.childNodes[0].childNodes.length, 1, "Shouldn't have the unverified marker");
|
||||
|
||||
// Wait for the complete notification
|
||||
wait_for_notification("addon-install-complete", function(aPanel) {
|
||||
AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(aAddon) {
|
||||
aAddon.uninstall();
|
||||
|
||||
Services.perms.remove("example.com", "install");
|
||||
wait_for_notification_close(runNextTest);
|
||||
gBrowser.removeTab(gBrowser.selectedTab);
|
||||
});
|
||||
});
|
||||
|
||||
accept_install_dialog();
|
||||
});
|
||||
});
|
||||
|
||||
var pm = Services.perms;
|
||||
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
|
||||
|
||||
var triggers = encodeURIComponent(JSON.stringify({
|
||||
"Extension XPI": "restartless.xpi"
|
||||
}));
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
|
||||
},
|
||||
|
||||
function test_url() {
|
||||
// Wait for the progress notification
|
||||
wait_for_progress_notification(function(aPanel) {
|
||||
|
@ -2773,6 +2773,16 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
</handlers>
|
||||
</binding>
|
||||
|
||||
<!-- This is the XBL notification definition for the login fill doorhanger,
|
||||
which is empty because the actual panel is not implemented inside an XBL
|
||||
binding, but made of elements added to the notification panel. This
|
||||
allows accessing the full structure while the panel is hidden. -->
|
||||
<binding id="password-fill-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
|
||||
<content>
|
||||
<children/>
|
||||
</content>
|
||||
</binding>
|
||||
|
||||
<binding id="splitmenu">
|
||||
<content>
|
||||
<xul:hbox anonid="menuitem" flex="1"
|
||||
|
@ -332,6 +332,7 @@ const PanelUI = {
|
||||
tempPanel.setAttribute("type", "arrow");
|
||||
tempPanel.setAttribute("id", "customizationui-widget-panel");
|
||||
tempPanel.setAttribute("class", "cui-widget-panel");
|
||||
tempPanel.setAttribute("viewId", aViewId);
|
||||
if (this._disableAnimations) {
|
||||
tempPanel.setAttribute("animate", "false");
|
||||
}
|
||||
|
@ -11,4 +11,15 @@ standalone/content/libs
|
||||
standalone/node_modules
|
||||
# Libs we don't need to check
|
||||
test/shared/vendor
|
||||
|
||||
# These are generated react files that we don't need to check
|
||||
content/js/contacts.js
|
||||
content/js/conversation.js
|
||||
content/js/conversationViews.js
|
||||
content/js/panel.js
|
||||
content/js/roomViews.js
|
||||
content/shared/js/feedbackViews.js
|
||||
content/shared/js/views.js
|
||||
standalone/content/js/fxOSMarketplace.js
|
||||
standalone/content/js/standaloneRoomViews.js
|
||||
standalone/content/js/webapp.js
|
||||
ui/ui-showcase.js
|
||||
|
@ -53,7 +53,6 @@
|
||||
"no-return-assign": 0, // TODO: Remove (use default)
|
||||
"no-shadow": 0, // TODO: Remove (use default)
|
||||
"no-spaced-func": 0, // TODO: Remove (use default)
|
||||
"no-trailing-spaces": 0, // TODO: Remove (use default)
|
||||
"no-undef": 0, // TODO: Remove (use default)
|
||||
"no-underscore-dangle": 0, // Leave as 0. Commonly used for private variables.
|
||||
"no-unused-expressions": 0, // TODO: Remove (use default)
|
||||
|
@ -512,7 +512,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
valueLink: this.linkState("newRoomDescription")})
|
||||
),
|
||||
React.createElement("button", {className: "room-context-btn-close",
|
||||
onClick: this.handleCloseClick})
|
||||
onClick: this.handleCloseClick,
|
||||
title: mozL10n.get("cancel_button")})
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -537,9 +538,11 @@ loop.roomViews = (function(mozL10n) {
|
||||
React.createElement("div", {className: "room-context-comment"}, this.props.roomData.roomDescription) :
|
||||
null,
|
||||
React.createElement("button", {className: "room-context-btn-close",
|
||||
onClick: this.handleCloseClick}),
|
||||
onClick: this.handleCloseClick,
|
||||
title: mozL10n.get("context_hide_tooltip")}),
|
||||
React.createElement("button", {className: "room-context-btn-edit",
|
||||
onClick: this.handleEditClick})
|
||||
onClick: this.handleEditClick,
|
||||
title: mozL10n.get("context_edit_tooltip")})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -512,7 +512,8 @@ loop.roomViews = (function(mozL10n) {
|
||||
valueLink={this.linkState("newRoomDescription")} />
|
||||
</form>
|
||||
<button className="room-context-btn-close"
|
||||
onClick={this.handleCloseClick}/>
|
||||
onClick={this.handleCloseClick}
|
||||
title={mozL10n.get("cancel_button")}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -537,9 +538,11 @@ loop.roomViews = (function(mozL10n) {
|
||||
<div className="room-context-comment">{this.props.roomData.roomDescription}</div> :
|
||||
null}
|
||||
<button className="room-context-btn-close"
|
||||
onClick={this.handleCloseClick}/>
|
||||
onClick={this.handleCloseClick}
|
||||
title={mozL10n.get("context_hide_tooltip")}/>
|
||||
<button className="room-context-btn-edit"
|
||||
onClick={this.handleEditClick}/>
|
||||
onClick={this.handleEditClick}
|
||||
title={mozL10n.get("context_edit_tooltip")}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -733,7 +733,9 @@ BrowserGlue.prototype = {
|
||||
LightweightThemeManager.addBuiltInTheme({
|
||||
id: "firefox-devedition@mozilla.org",
|
||||
name: themeName,
|
||||
#ifdef XP_WIN
|
||||
accentcolor: "transparent",
|
||||
#endif
|
||||
headerURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.header.png",
|
||||
iconURL: "resource:///chrome/browser/content/browser/defaultthemes/devedition.icon.png",
|
||||
author: vendorShortName,
|
||||
@ -2789,8 +2791,11 @@ let E10SUINotification = {
|
||||
checkStatus: function() {
|
||||
let skipE10sChecks = false;
|
||||
try {
|
||||
skipE10sChecks = (UpdateChannel.get() != "nightly") ||
|
||||
Services.prefs.getBoolPref("browser.tabs.remote.autostart.disabled-because-using-a11y");
|
||||
let updateChannel = UpdateChannel.get();
|
||||
let channelAuthorized = updateChannel == "nightly" || updateChannel == "aurora";
|
||||
|
||||
skipE10sChecks = !channelAuthorized ||
|
||||
UpdateServices.prefs.getBoolPref("browser.tabs.remote.autostart.disabled-because-using-a11y");
|
||||
} catch(e) {}
|
||||
|
||||
if (skipE10sChecks) {
|
||||
@ -2909,7 +2914,7 @@ let E10SUINotification = {
|
||||
|
||||
let browser = win.gBrowser.selectedBrowser;
|
||||
|
||||
let promptMessage = "Would you like to help us test multiprocess Nightly (e10s)? You can also enable e10s in Nightly preferences. Notable fixes:";
|
||||
let promptMessage = "Multi-process is coming soon to Firefox. You can start using it now to get early access to some of the benefits:";
|
||||
let mainAction = {
|
||||
label: "Enable and Restart",
|
||||
accessKey: "E",
|
||||
@ -2942,9 +2947,8 @@ let E10SUINotification = {
|
||||
win.PopupNotifications.show(browser, "enable-e10s", promptMessage, null, mainAction, secondaryActions, options);
|
||||
|
||||
let highlights = [
|
||||
"Less crashing!",
|
||||
"Improved add-on compatibility and DevTools",
|
||||
"PDF.js, Web Console, Spellchecking, WebRTC now work"
|
||||
"Improved responsiveness",
|
||||
"Fewer crashes"
|
||||
];
|
||||
|
||||
let doorhangerExtraContent = win.document.getElementById("enable-e10s-notification")
|
||||
|
@ -82,12 +82,18 @@ let gEditItemOverlay = {
|
||||
|
||||
// Check if the pane is initialized to show only read-only fields.
|
||||
get readOnly() {
|
||||
// Bug 1120314 - Folder shortcuts are read-only due to some quirky implementation
|
||||
// details (the most important being the "smart" semantics of node.title).
|
||||
return (!this.initialized ||
|
||||
(!this._paneInfo.visibleRows.has("tagsRow") &&
|
||||
(this._paneInfo.isFolderShortcut ||
|
||||
this._paneInfo.isParentReadOnly)));
|
||||
// TODO (Bug 1120314): Folder shortcuts are currently read-only due to some
|
||||
// quirky implementation details (the most important being the "smart"
|
||||
// semantics of node.title that makes hard to edit the right entry).
|
||||
// This pane is read-only if:
|
||||
// * the panel is not initialized
|
||||
// * the node is a folder shortcut
|
||||
// * the node is not bookmarked
|
||||
// * the node is child of a read-only container and is not a bookmarked URI
|
||||
return !this.initialized ||
|
||||
this._paneInfo.isFolderShortcut ||
|
||||
!this._paneInfo.isItem ||
|
||||
(this._paneInfo.isParentReadOnly && !this._paneInfo.isBookmark);
|
||||
},
|
||||
|
||||
// the first field which was edited after this panel was initialized for
|
||||
@ -184,19 +190,25 @@ let gEditItemOverlay = {
|
||||
this._namePicker.readOnly = this.readOnly;
|
||||
}
|
||||
|
||||
if (showOrCollapse("locationRow", isURI, "location")) {
|
||||
// In some cases we want to hide the location field, since it's not
|
||||
// human-readable, but we still want to initialize it.
|
||||
showOrCollapse("locationRow", isURI, "location");
|
||||
if (isURI) {
|
||||
this._initLocationField();
|
||||
this._locationField.readOnly = !this._paneInfo.isItem;
|
||||
this._locationField.readOnly = !this.readOnly;
|
||||
}
|
||||
|
||||
if (showOrCollapse("descriptionRow",
|
||||
this._paneInfo.isItem && !this.readOnly,
|
||||
// hide the description field for
|
||||
if (showOrCollapse("descriptionRow", isItem && !this.readOnly,
|
||||
"description")) {
|
||||
this._initDescriptionField();
|
||||
this._descriptionField.readOnly = this.readOnly;
|
||||
}
|
||||
|
||||
if (showOrCollapse("keywordRow", isBookmark, "keyword"))
|
||||
if (showOrCollapse("keywordRow", isBookmark, "keyword")) {
|
||||
this._initKeywordField();
|
||||
this._keywordField.readOnly = this.readOnly;
|
||||
}
|
||||
|
||||
// Collapse the tag selector if the item does not accept tags.
|
||||
if (showOrCollapse("tagsRow", isURI || bulkTagging, "tags"))
|
||||
@ -355,7 +367,7 @@ let gEditItemOverlay = {
|
||||
|
||||
// Hide the folders-separator if no folder is annotated as recently-used
|
||||
this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6);
|
||||
this._folderMenuList.disabled = this._readOnly;
|
||||
this._folderMenuList.disabled = this.readOnly;
|
||||
},
|
||||
|
||||
QueryInterface:
|
||||
@ -387,7 +399,7 @@ let gEditItemOverlay = {
|
||||
},
|
||||
|
||||
onTagsFieldChange() {
|
||||
if (!this.readOnly) {
|
||||
if (this._paneInfo.isURI || this._paneInfo.bulkTagging) {
|
||||
this._updateTags().then(
|
||||
anyChanges => {
|
||||
if (anyChanges)
|
||||
|
@ -15,13 +15,28 @@ XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
|
||||
"resource://gre/modules/ReaderMode.jsm");
|
||||
|
||||
let Pocket = {
|
||||
get site() Services.prefs.getCharPref("browser.pocket.site"),
|
||||
get listURL() { return "https://" + Pocket.site; },
|
||||
|
||||
/**
|
||||
* Functions related to the Pocket panel UI.
|
||||
*/
|
||||
onPanelViewShowing(event) {
|
||||
let window = event.target.ownerDocument.defaultView;
|
||||
window.addEventListener("popupshowing", Pocket.onPocketPanelShowing, true);
|
||||
window.addEventListener("popupshown", Pocket.onPocketPanelShown, true);
|
||||
},
|
||||
|
||||
onPocketPanelShowing(event) {
|
||||
let window = event.target.ownerDocument.defaultView;
|
||||
window.removeEventListener("popupshowing", Pocket.onPocketPanelShowing, true);
|
||||
window.pktUI.pocketButtonOnCommand(event);
|
||||
window.pktUI.pocketPanelDidShow(event)
|
||||
},
|
||||
|
||||
onPocketPanelShown(event) {
|
||||
let window = event.target.ownerDocument.defaultView;
|
||||
window.removeEventListener("popupshown", Pocket.onPocketPanelShown, true);
|
||||
window.pktUI.pocketPanelDidShow(event);
|
||||
},
|
||||
|
||||
onPanelViewHiding(event) {
|
||||
|
@ -301,12 +301,10 @@ var pktUI = (function() {
|
||||
}
|
||||
showPanel("chrome://browser/content/pocket/panels/signup.html?pockethost=" + Services.prefs.getCharPref("browser.pocket.site") + "&fxasignedin=" + fxasignedin + "&variant=" + pktApi.getSignupAB(), {
|
||||
onShow: function() {
|
||||
resizePanel({
|
||||
width: 300,
|
||||
height: startheight
|
||||
});
|
||||
},
|
||||
onHide: panelDidHide,
|
||||
width: 300,
|
||||
height: startheight
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -325,12 +323,6 @@ var pktUI = (function() {
|
||||
|
||||
showPanel("chrome://browser/content/pocket/panels/saved.html?pockethost=" + Services.prefs.getCharPref("browser.pocket.site") + "&premiumStatus=" + (pktApi.isPremiumUser() ? '1' : '0'), {
|
||||
onShow: function() {
|
||||
// Open and resize the panel
|
||||
resizePanel({
|
||||
width: 350,
|
||||
height: 263
|
||||
});
|
||||
|
||||
// Send error message for invalid url
|
||||
if (!isValidURL) {
|
||||
var error = new Error('Only links can be saved');
|
||||
@ -369,6 +361,8 @@ var pktUI = (function() {
|
||||
pktApi.addLink(url, options);
|
||||
},
|
||||
onHide: panelDidHide,
|
||||
width: 350,
|
||||
height: 267
|
||||
});
|
||||
}
|
||||
|
||||
@ -397,6 +391,11 @@ var pktUI = (function() {
|
||||
// do it this hacky way for now
|
||||
currentPanelDidShow = options.onShow;
|
||||
currentPanelDidHide = options.onHide;
|
||||
|
||||
resizePanel({
|
||||
width: options.width,
|
||||
height: options.height
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -411,15 +410,9 @@ var pktUI = (function() {
|
||||
var iframe = getPanelFrame();
|
||||
iframe.width = options.width;
|
||||
iframe.height = options.height;
|
||||
return;
|
||||
|
||||
// TODO : Animate the change if given options.animate = true
|
||||
getPanel().sizeTo(options.width, options.height);
|
||||
setTimeout(function(){
|
||||
// we set the iframe size directly because it does not automatically stretch vertically
|
||||
var height = document.getElementById('pocket-panel-container').clientHeight + 'px';
|
||||
getPanelFrame().style.height = height;
|
||||
},1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,9 +36,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Utils",
|
||||
* In a typical restore, content-sessionStore.js will call the following based
|
||||
* on messages and events it receives:
|
||||
*
|
||||
* restoreHistory(epoch, tabData, callbacks)
|
||||
* restoreHistory(tabData, loadArguments, callbacks)
|
||||
* Restores the tab's history and session cookies.
|
||||
* restoreTabContent(finishCallback)
|
||||
* restoreTabContent(loadArguments, finishCallback)
|
||||
* Starts loading the data for the current page to restore.
|
||||
* restoreDocument()
|
||||
* Restore form and scroll data.
|
||||
@ -54,11 +54,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "Utils",
|
||||
* At any time, SessionStore.jsm can cancel the ongoing restore by sending a
|
||||
* reset message, which causes resetRestore to be called. At that point it's
|
||||
* legal to begin another restore.
|
||||
*
|
||||
* The epoch that is passed into restoreHistory is merely a token. All messages
|
||||
* sent back to SessionStore.jsm include the epoch. This way, SessionStore.jsm
|
||||
* can discard messages that relate to restores that it has canceled (by
|
||||
* starting a new restore, say).
|
||||
*/
|
||||
function ContentRestore(chromeGlobal) {
|
||||
let internal = new ContentRestoreInternal(chromeGlobal);
|
||||
@ -67,8 +62,7 @@ function ContentRestore(chromeGlobal) {
|
||||
let EXPORTED_METHODS = ["restoreHistory",
|
||||
"restoreTabContent",
|
||||
"restoreDocument",
|
||||
"resetRestore",
|
||||
"getRestoreEpoch",
|
||||
"resetRestore"
|
||||
];
|
||||
|
||||
for (let method of EXPORTED_METHODS) {
|
||||
@ -84,9 +78,6 @@ function ContentRestoreInternal(chromeGlobal) {
|
||||
// The following fields are only valid during certain phases of the restore
|
||||
// process.
|
||||
|
||||
// The epoch that was passed into restoreHistory. Removed in restoreDocument.
|
||||
this._epoch = 0;
|
||||
|
||||
// The tabData for the restore. Set in restoreHistory and removed in
|
||||
// restoreTabContent.
|
||||
this._tabData = null;
|
||||
@ -123,9 +114,8 @@ ContentRestoreInternal.prototype = {
|
||||
* non-zero) is passed through to all the callbacks. If a load in the tab
|
||||
* is started while it is pending, the appropriate callbacks are called.
|
||||
*/
|
||||
restoreHistory(epoch, tabData, loadArguments, callbacks) {
|
||||
restoreHistory(tabData, loadArguments, callbacks) {
|
||||
this._tabData = tabData;
|
||||
this._epoch = epoch;
|
||||
|
||||
// In case about:blank isn't done yet.
|
||||
let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
|
||||
@ -290,8 +280,6 @@ ContentRestoreInternal.prototype = {
|
||||
* called when the "load" event fires for the restoring tab.
|
||||
*/
|
||||
restoreDocument: function () {
|
||||
this._epoch = 0;
|
||||
|
||||
if (!this._restoringDocument) {
|
||||
return;
|
||||
}
|
||||
@ -327,15 +315,7 @@ ContentRestoreInternal.prototype = {
|
||||
this._progressListener.uninstall();
|
||||
}
|
||||
this._progressListener = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* If a restore is ongoing, this function returns the value of |epoch| that
|
||||
* was passed to restoreHistory. If no restore is ongoing, it returns 0.
|
||||
*/
|
||||
getRestoreEpoch: function () {
|
||||
return this._epoch;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -88,6 +88,16 @@ const NOTAB_MESSAGES = new Set([
|
||||
"SessionStore:update",
|
||||
]);
|
||||
|
||||
// The list of messages we accept without an "epoch" parameter.
|
||||
// See getCurrentEpoch() and friends to find out what an "epoch" is.
|
||||
const NOEPOCH_MESSAGES = new Set([
|
||||
// For a description see above.
|
||||
"SessionStore:setupSyncHandler",
|
||||
|
||||
// For a description see above.
|
||||
"SessionStore:crashedTabRevived",
|
||||
]);
|
||||
|
||||
// The list of messages we want to receive even during the short period after a
|
||||
// frame has been removed from the DOM and before its frame script has finished
|
||||
// unloading.
|
||||
@ -113,6 +123,7 @@ Cu.import("resource://gre/modules/osfile.jsm", this);
|
||||
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
|
||||
Cu.import("resource://gre/modules/Promise.jsm", this);
|
||||
Cu.import("resource://gre/modules/Task.jsm", this);
|
||||
Cu.import("resource://gre/modules/debug.js", this);
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
|
||||
"@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
|
||||
@ -329,10 +340,7 @@ let SessionStoreInternal = {
|
||||
// windows yet to be restored
|
||||
_restoreCount: -1,
|
||||
|
||||
// This number gets incremented each time we start to restore a tab.
|
||||
_nextRestoreEpoch: 1,
|
||||
|
||||
// For each <browser> element being restored, records the current epoch.
|
||||
// For each <browser> element, records the current epoch.
|
||||
_browserEpochs: new WeakMap(),
|
||||
|
||||
// Any browsers that fires the oop-browser-crashed event gets stored in
|
||||
@ -615,6 +623,19 @@ let SessionStoreInternal = {
|
||||
`from a browser that has no tab`);
|
||||
}
|
||||
|
||||
let data = aMessage.data || {};
|
||||
let hasEpoch = data.hasOwnProperty("epoch");
|
||||
|
||||
// Most messages sent by frame scripts require to pass an epoch.
|
||||
if (!hasEpoch && !NOEPOCH_MESSAGES.has(aMessage.name)) {
|
||||
throw new Error(`received message '${aMessage.name}' without an epoch`);
|
||||
}
|
||||
|
||||
// Ignore messages from previous epochs.
|
||||
if (hasEpoch && !this.isCurrentEpoch(browser, data.epoch)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (aMessage.name) {
|
||||
case "SessionStore:setupSyncHandler":
|
||||
TabState.setSyncHandler(browser, aMessage.objects.handler);
|
||||
@ -680,77 +701,69 @@ let SessionStoreInternal = {
|
||||
}
|
||||
break;
|
||||
case "SessionStore:restoreHistoryComplete":
|
||||
if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
|
||||
// Notify the tabbrowser that the tab chrome has been restored.
|
||||
let tabData = browser.__SS_data;
|
||||
// Notify the tabbrowser that the tab chrome has been restored.
|
||||
let tabData = browser.__SS_data;
|
||||
|
||||
// wall-paper fix for bug 439675: make sure that the URL to be loaded
|
||||
// is always visible in the address bar
|
||||
let activePageData = tabData.entries[tabData.index - 1] || null;
|
||||
let uri = activePageData ? activePageData.url || null : null;
|
||||
browser.userTypedValue = uri;
|
||||
// wall-paper fix for bug 439675: make sure that the URL to be loaded
|
||||
// is always visible in the address bar
|
||||
let activePageData = tabData.entries[tabData.index - 1] || null;
|
||||
let uri = activePageData ? activePageData.url || null : null;
|
||||
browser.userTypedValue = uri;
|
||||
|
||||
// If the page has a title, set it.
|
||||
if (activePageData) {
|
||||
if (activePageData.title) {
|
||||
tab.label = activePageData.title;
|
||||
tab.crop = "end";
|
||||
} else if (activePageData.url != "about:blank") {
|
||||
tab.label = activePageData.url;
|
||||
tab.crop = "center";
|
||||
}
|
||||
// If the page has a title, set it.
|
||||
if (activePageData) {
|
||||
if (activePageData.title) {
|
||||
tab.label = activePageData.title;
|
||||
tab.crop = "end";
|
||||
} else if (activePageData.url != "about:blank") {
|
||||
tab.label = activePageData.url;
|
||||
tab.crop = "center";
|
||||
}
|
||||
|
||||
// Restore the tab icon.
|
||||
if ("image" in tabData) {
|
||||
win.gBrowser.setIcon(tab, tabData.image);
|
||||
}
|
||||
|
||||
let event = win.document.createEvent("Events");
|
||||
event.initEvent("SSTabRestoring", true, false);
|
||||
tab.dispatchEvent(event);
|
||||
}
|
||||
|
||||
// Restore the tab icon.
|
||||
if ("image" in tabData) {
|
||||
win.gBrowser.setIcon(tab, tabData.image);
|
||||
}
|
||||
|
||||
let event = win.document.createEvent("Events");
|
||||
event.initEvent("SSTabRestoring", true, false);
|
||||
tab.dispatchEvent(event);
|
||||
break;
|
||||
case "SessionStore:restoreTabContentStarted":
|
||||
if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
|
||||
if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
|
||||
// If a load not initiated by sessionstore was started in a
|
||||
// previously pending tab. Mark the tab as no longer pending.
|
||||
this.markTabAsRestoring(tab);
|
||||
} else {
|
||||
// If the user was typing into the URL bar when we crashed, but hadn't hit
|
||||
// enter yet, then we just need to write that value to the URL bar without
|
||||
// loading anything. This must happen after the load, since it will clear
|
||||
// userTypedValue.
|
||||
let tabData = browser.__SS_data;
|
||||
if (tabData.userTypedValue && !tabData.userTypedClear) {
|
||||
browser.userTypedValue = tabData.userTypedValue;
|
||||
win.URLBarSetURI();
|
||||
}
|
||||
if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
|
||||
// If a load not initiated by sessionstore was started in a
|
||||
// previously pending tab. Mark the tab as no longer pending.
|
||||
this.markTabAsRestoring(tab);
|
||||
} else {
|
||||
// If the user was typing into the URL bar when we crashed, but hadn't hit
|
||||
// enter yet, then we just need to write that value to the URL bar without
|
||||
// loading anything. This must happen after the load, since it will clear
|
||||
// userTypedValue.
|
||||
let tabData = browser.__SS_data;
|
||||
if (tabData.userTypedValue && !tabData.userTypedClear) {
|
||||
browser.userTypedValue = tabData.userTypedValue;
|
||||
win.URLBarSetURI();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "SessionStore:restoreTabContentComplete":
|
||||
if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
|
||||
// This callback is used exclusively by tests that want to
|
||||
// monitor the progress of network loads.
|
||||
if (gDebuggingEnabled) {
|
||||
Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null);
|
||||
}
|
||||
|
||||
delete browser.__SS_data;
|
||||
|
||||
SessionStoreInternal._resetLocalTabRestoringState(tab);
|
||||
SessionStoreInternal.restoreNextTab();
|
||||
|
||||
this._sendTabRestoredNotification(tab);
|
||||
// This callback is used exclusively by tests that want to
|
||||
// monitor the progress of network loads.
|
||||
if (gDebuggingEnabled) {
|
||||
Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null);
|
||||
}
|
||||
|
||||
delete browser.__SS_data;
|
||||
|
||||
SessionStoreInternal._resetLocalTabRestoringState(tab);
|
||||
SessionStoreInternal.restoreNextTab();
|
||||
|
||||
this._sendTabRestoredNotification(tab);
|
||||
break;
|
||||
case "SessionStore:reloadPendingTab":
|
||||
if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
|
||||
if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
|
||||
this.restoreTabContent(tab);
|
||||
}
|
||||
if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
|
||||
this.restoreTabContent(tab);
|
||||
}
|
||||
break;
|
||||
case "SessionStore:crashedTabRevived":
|
||||
@ -1613,8 +1626,10 @@ let SessionStoreInternal = {
|
||||
// restore the selected tab and lazily restore the rest. We'll make no
|
||||
// efforts at this time to be smart and restore all of the tabs that had
|
||||
// been in a restored state at the time of the crash.
|
||||
let tab = aWindow.gBrowser.getTabForBrowser(aBrowser);
|
||||
this._resetLocalTabRestoringState(tab);
|
||||
if (aBrowser.__SS_restoreState) {
|
||||
let tab = aWindow.gBrowser.getTabForBrowser(aBrowser);
|
||||
this._resetLocalTabRestoringState(tab);
|
||||
}
|
||||
},
|
||||
|
||||
// Clean up data that has been closed a long time ago.
|
||||
@ -2728,6 +2743,9 @@ let SessionStoreInternal = {
|
||||
|
||||
// Restores the given tab state for a given tab.
|
||||
restoreTab(tab, tabData, options = {}) {
|
||||
NS_ASSERT(!tab.linkedBrowser.__SS_restoreState,
|
||||
"must reset tab before calling restoreTab()");
|
||||
|
||||
let restoreImmediately = options.restoreImmediately;
|
||||
let loadArguments = options.loadArguments;
|
||||
let browser = tab.linkedBrowser;
|
||||
@ -2788,11 +2806,6 @@ let SessionStoreInternal = {
|
||||
// Tab is now open.
|
||||
delete tabData.closedAt;
|
||||
|
||||
// Flush all data from the content script synchronously. This is done so
|
||||
// that all async messages that are still on their way to chrome will
|
||||
// be ignored and don't override any tab data set when restoring.
|
||||
TabState.flush(browser);
|
||||
|
||||
// Ensure the index is in bounds.
|
||||
let activeIndex = (tabData.index || tabData.entries.length) - 1;
|
||||
activeIndex = Math.min(activeIndex, tabData.entries.length - 1);
|
||||
@ -2810,11 +2823,10 @@ let SessionStoreInternal = {
|
||||
}
|
||||
tabbrowser.updateBrowserRemotenessByURL(browser, uri);
|
||||
|
||||
// Start a new epoch and include the epoch in the restoreHistory
|
||||
// message. If a message is received that relates to a previous epoch, we
|
||||
// discard it.
|
||||
let epoch = this._nextRestoreEpoch++;
|
||||
this._browserEpochs.set(browser.permanentKey, epoch);
|
||||
// Start a new epoch to discard all frame script messages relating to a
|
||||
// previous epoch. All async messages that are still on their way to chrome
|
||||
// will be ignored and don't override any tab data set when restoring.
|
||||
let epoch = this.startNextEpoch(browser);
|
||||
|
||||
// keep the data around to prevent dataloss in case
|
||||
// a tab gets closed before it's been properly restored
|
||||
@ -3590,6 +3602,9 @@ let SessionStoreInternal = {
|
||||
* The tab that will be "reset"
|
||||
*/
|
||||
_resetLocalTabRestoringState: function (aTab) {
|
||||
NS_ASSERT(aTab.linkedBrowser.__SS_restoreState,
|
||||
"given tab is not restoring");
|
||||
|
||||
let window = aTab.ownerDocument.defaultView;
|
||||
let browser = aTab.linkedBrowser;
|
||||
|
||||
@ -3598,7 +3613,6 @@ let SessionStoreInternal = {
|
||||
|
||||
// The browser is no longer in any sort of restoring state.
|
||||
delete browser.__SS_restoreState;
|
||||
this._browserEpochs.delete(browser.permanentKey);
|
||||
|
||||
aTab.removeAttribute("pending");
|
||||
browser.removeAttribute("pending");
|
||||
@ -3615,13 +3629,34 @@ let SessionStoreInternal = {
|
||||
},
|
||||
|
||||
_resetTabRestoringState: function (tab) {
|
||||
NS_ASSERT(tab.linkedBrowser.__SS_restoreState,
|
||||
"given tab is not restoring");
|
||||
|
||||
let browser = tab.linkedBrowser;
|
||||
if (browser.__SS_restoreState) {
|
||||
browser.messageManager.sendAsyncMessage("SessionStore:resetRestore", {});
|
||||
}
|
||||
browser.messageManager.sendAsyncMessage("SessionStore:resetRestore", {});
|
||||
this._resetLocalTabRestoringState(tab);
|
||||
},
|
||||
|
||||
/**
|
||||
* Each fresh tab starts out with epoch=0. This function can be used to
|
||||
* start a next epoch by incrementing the current value. It will enables us
|
||||
* to ignore stale messages sent from previous epochs. The function returns
|
||||
* the new epoch ID for the given |browser|.
|
||||
*/
|
||||
startNextEpoch(browser) {
|
||||
let next = this.getCurrentEpoch(browser) + 1;
|
||||
this._browserEpochs.set(browser.permanentKey, next);
|
||||
return next;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the current epoch for the given <browser>. If we haven't assigned
|
||||
* a new epoch this will default to zero for new tabs.
|
||||
*/
|
||||
getCurrentEpoch(browser) {
|
||||
return this._browserEpochs.get(browser.permanentKey) || 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Each time a <browser> element is restored, we increment its "epoch". To
|
||||
* check if a message from content-sessionStore.js is out of date, we can
|
||||
@ -3630,7 +3665,7 @@ let SessionStoreInternal = {
|
||||
* with respect to |browser|.
|
||||
*/
|
||||
isCurrentEpoch: function (browser, epoch) {
|
||||
return (this._browserEpochs.get(browser.permanentKey) || 0) == epoch;
|
||||
return this.getCurrentEpoch(browser) == epoch;
|
||||
},
|
||||
|
||||
};
|
||||
|
@ -40,6 +40,9 @@ Cu.import("resource:///modules/sessionstore/ContentRestore.jsm", this);
|
||||
XPCOMUtils.defineLazyGetter(this, 'gContentRestore',
|
||||
() => { return new ContentRestore(this) });
|
||||
|
||||
// The current epoch.
|
||||
let gCurrentEpoch = 0;
|
||||
|
||||
/**
|
||||
* Returns a lazy function that will evaluate the given
|
||||
* function |fn| only once and cache its return value.
|
||||
@ -88,14 +91,8 @@ let EventListener = {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're in the process of restoring, this load may signal
|
||||
// the end of the restoration.
|
||||
let epoch = gContentRestore.getRestoreEpoch();
|
||||
if (!epoch) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Restore the form data and scroll position.
|
||||
// Restore the form data and scroll position. If we're not currently
|
||||
// restoring a tab state then this call will simply be a noop.
|
||||
gContentRestore.restoreDocument();
|
||||
}
|
||||
};
|
||||
@ -122,6 +119,14 @@ let MessageListener = {
|
||||
return;
|
||||
}
|
||||
|
||||
// A fresh tab always starts with epoch=0. The parent has the ability to
|
||||
// override that to signal a new era in this tab's life. This enables it
|
||||
// to ignore async messages that were already sent but not yet received
|
||||
// and would otherwise confuse the internal tab state.
|
||||
if (data.epoch && data.epoch != gCurrentEpoch) {
|
||||
gCurrentEpoch = data.epoch;
|
||||
}
|
||||
|
||||
switch (name) {
|
||||
case "SessionStore:restoreHistory":
|
||||
this.restoreHistory(data);
|
||||
@ -139,7 +144,7 @@ let MessageListener = {
|
||||
},
|
||||
|
||||
restoreHistory({epoch, tabData, loadArguments}) {
|
||||
gContentRestore.restoreHistory(epoch, tabData, loadArguments, {
|
||||
gContentRestore.restoreHistory(tabData, loadArguments, {
|
||||
onReload() {
|
||||
// Inform SessionStore.jsm about the reload. It will send
|
||||
// restoreTabContent in response.
|
||||
@ -172,7 +177,7 @@ let MessageListener = {
|
||||
},
|
||||
|
||||
restoreTabContent({loadArguments}) {
|
||||
let epoch = gContentRestore.getRestoreEpoch();
|
||||
let epoch = gCurrentEpoch;
|
||||
|
||||
// We need to pass the value of didStartLoad back to SessionStore.jsm.
|
||||
let didStartLoad = gContentRestore.restoreTabContent(loadArguments, () => {
|
||||
@ -698,7 +703,8 @@ let MessageQueue = {
|
||||
// Send all data to the parent process.
|
||||
sendMessage("SessionStore:update", {
|
||||
id: this._id, data, telemetry,
|
||||
isFinal: options.isFinal || false
|
||||
isFinal: options.isFinal || false,
|
||||
epoch: gCurrentEpoch
|
||||
});
|
||||
|
||||
// Increase our unique message ID.
|
||||
|
@ -165,6 +165,7 @@ function TabTarget(tab) {
|
||||
this._handleThreadState = this._handleThreadState.bind(this);
|
||||
this.on("thread-resumed", this._handleThreadState);
|
||||
this.on("thread-paused", this._handleThreadState);
|
||||
this.activeTab = this.activeConsole = null;
|
||||
// Only real tabs need initialization here. Placeholder objects for remote
|
||||
// targets will be initialized after a makeRemote method call.
|
||||
if (tab && !["client", "form", "chrome"].every(tab.hasOwnProperty, tab)) {
|
||||
@ -420,6 +421,19 @@ TabTarget.prototype = {
|
||||
}
|
||||
this.activeTab = aTabClient;
|
||||
this.threadActor = aResponse.threadActor;
|
||||
attachConsole();
|
||||
});
|
||||
};
|
||||
|
||||
let attachConsole = () => {
|
||||
this._client.attachConsole(this._form.consoleActor,
|
||||
[ "NetworkActivity" ],
|
||||
(aResponse, aWebConsoleClient) => {
|
||||
if (!aWebConsoleClient) {
|
||||
this._remote.reject("Unable to attach to the console");
|
||||
return;
|
||||
}
|
||||
this.activeConsole = aWebConsoleClient;
|
||||
this._remote.resolve(null);
|
||||
});
|
||||
};
|
||||
@ -439,7 +453,7 @@ TabTarget.prototype = {
|
||||
} else {
|
||||
// AddonActor and chrome debugging on RootActor doesn't inherits from TabActor and
|
||||
// doesn't need to be attached.
|
||||
this._remote.resolve(null);
|
||||
attachConsole();
|
||||
}
|
||||
|
||||
return this._remote.promise;
|
||||
@ -593,17 +607,15 @@ TabTarget.prototype = {
|
||||
// We started with a local tab and created the client ourselves, so we
|
||||
// should close it.
|
||||
this._client.close(cleanupAndResolve);
|
||||
} else {
|
||||
} else if (this.activeTab) {
|
||||
// The client was handed to us, so we are not responsible for closing
|
||||
// it. We just need to detach from the tab, if already attached.
|
||||
if (this.activeTab) {
|
||||
// |detach| may fail if the connection is already dead, so proceed
|
||||
// cleanup directly after this.
|
||||
this.activeTab.detach();
|
||||
cleanupAndResolve();
|
||||
} else {
|
||||
cleanupAndResolve();
|
||||
}
|
||||
// |detach| may fail if the connection is already dead, so proceed with
|
||||
// cleanup directly after this.
|
||||
this.activeTab.detach();
|
||||
cleanupAndResolve();
|
||||
} else {
|
||||
cleanupAndResolve();
|
||||
}
|
||||
}
|
||||
|
||||
@ -620,6 +632,7 @@ TabTarget.prototype = {
|
||||
promiseTargets.delete(this._form);
|
||||
}
|
||||
this.activeTab = null;
|
||||
this.activeConsole = null;
|
||||
this._client = null;
|
||||
this._tab = null;
|
||||
this._form = null;
|
||||
|
@ -110,6 +110,9 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
|
||||
this._toolPanels = new Map();
|
||||
this._telemetry = new Telemetry();
|
||||
|
||||
this._initInspector = null;
|
||||
this._inspector = null;
|
||||
|
||||
this._toolRegistered = this._toolRegistered.bind(this);
|
||||
this._toolUnregistered = this._toolUnregistered.bind(this);
|
||||
this._refreshHostTitle = this._refreshHostTitle.bind(this);
|
||||
@ -367,7 +370,7 @@ Toolbox.prototype = {
|
||||
|
||||
this._pingTelemetry();
|
||||
|
||||
let panel = yield this.selectTool(this._defaultToolId);
|
||||
yield this.selectTool(this._defaultToolId);
|
||||
|
||||
// Wait until the original tool is selected so that the split
|
||||
// console input will receive focus.
|
||||
|
@ -196,7 +196,9 @@ let NetMonitorController = {
|
||||
|
||||
/**
|
||||
* Initiates remote or chrome network monitoring based on the current target,
|
||||
* wiring event handlers as necessary.
|
||||
* wiring event handlers as necessary. Since the TabTarget will have already
|
||||
* started listening to network requests by now, this is largely
|
||||
* netmonitor-specific initialization.
|
||||
*
|
||||
* @return object
|
||||
* A promise that is resolved when the monitor finishes connecting.
|
||||
@ -209,15 +211,18 @@ let NetMonitorController = {
|
||||
let deferred = promise.defer();
|
||||
this._connection = deferred.promise;
|
||||
|
||||
let target = this._target;
|
||||
let { client, form } = target;
|
||||
this.client = this._target.client;
|
||||
// Some actors like AddonActor or RootActor for chrome debugging
|
||||
// do not support attach/detach and can be used directly
|
||||
if (!target.isTabActor) {
|
||||
this._startChromeMonitoring(client, form.consoleActor, deferred.resolve);
|
||||
} else {
|
||||
this._startMonitoringTab(client, form, deferred.resolve);
|
||||
// aren't actual tabs.
|
||||
if (this._target.isTabActor) {
|
||||
this.tabClient = this._target.activeTab;
|
||||
}
|
||||
this.webConsoleClient = this._target.activeConsole;
|
||||
this.webConsoleClient.setPreferences(NET_PREFS, () => {
|
||||
this.TargetEventsHandler.connect();
|
||||
this.NetworkEventsHandler.connect();
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
yield deferred.promise;
|
||||
window.emit(EVENTS.CONNECTED);
|
||||
@ -243,82 +248,6 @@ let NetMonitorController = {
|
||||
return !!this.client;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up a monitoring session.
|
||||
*
|
||||
* @param DebuggerClient aClient
|
||||
* The debugger client.
|
||||
* @param object aTabGrip
|
||||
* The remote protocol grip of the tab.
|
||||
* @param function aCallback
|
||||
* A function to invoke once the client attached to the console client.
|
||||
*/
|
||||
_startMonitoringTab: function(aClient, aTabGrip, aCallback) {
|
||||
if (!aClient) {
|
||||
Cu.reportError("No client found!");
|
||||
return;
|
||||
}
|
||||
this.client = aClient;
|
||||
|
||||
aClient.attachTab(aTabGrip.actor, (aResponse, aTabClient) => {
|
||||
if (!aTabClient) {
|
||||
Cu.reportError("No tab client found!");
|
||||
return;
|
||||
}
|
||||
this.tabClient = aTabClient;
|
||||
|
||||
aClient.attachConsole(aTabGrip.consoleActor, LISTENERS, (aResponse, aWebConsoleClient) => {
|
||||
if (!aWebConsoleClient) {
|
||||
Cu.reportError("Couldn't attach to console: " + aResponse.error);
|
||||
return;
|
||||
}
|
||||
this.webConsoleClient = aWebConsoleClient;
|
||||
this.webConsoleClient.setPreferences(NET_PREFS, () => {
|
||||
this.TargetEventsHandler.connect();
|
||||
this.NetworkEventsHandler.connect();
|
||||
|
||||
if (aCallback) {
|
||||
aCallback();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up a chrome monitoring session.
|
||||
*
|
||||
* @param DebuggerClient aClient
|
||||
* The debugger client.
|
||||
* @param object aConsoleActor
|
||||
* The remote protocol grip of the chrome debugger.
|
||||
* @param function aCallback
|
||||
* A function to invoke once the client attached to the console client.
|
||||
*/
|
||||
_startChromeMonitoring: function(aClient, aConsoleActor, aCallback) {
|
||||
if (!aClient) {
|
||||
Cu.reportError("No client found!");
|
||||
return;
|
||||
}
|
||||
this.client = aClient;
|
||||
|
||||
aClient.attachConsole(aConsoleActor, LISTENERS, (aResponse, aWebConsoleClient) => {
|
||||
if (!aWebConsoleClient) {
|
||||
Cu.reportError("Couldn't attach to console: " + aResponse.error);
|
||||
return;
|
||||
}
|
||||
this.webConsoleClient = aWebConsoleClient;
|
||||
this.webConsoleClient.setPreferences(NET_PREFS, () => {
|
||||
this.TargetEventsHandler.connect();
|
||||
this.NetworkEventsHandler.connect();
|
||||
|
||||
if (aCallback) {
|
||||
aCallback();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the activity currently performed by the frontend.
|
||||
* @return number
|
||||
@ -440,7 +369,6 @@ function TargetEventsHandler() {
|
||||
|
||||
TargetEventsHandler.prototype = {
|
||||
get target() NetMonitorController._target,
|
||||
get webConsoleClient() NetMonitorController.webConsoleClient,
|
||||
|
||||
/**
|
||||
* Listen for events emitted by the current tab target.
|
||||
@ -528,8 +456,28 @@ NetworkEventsHandler.prototype = {
|
||||
*/
|
||||
connect: function() {
|
||||
dumpn("NetworkEventsHandler is connecting...");
|
||||
this.client.addListener("networkEvent", this._onNetworkEvent);
|
||||
this.client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
this.webConsoleClient.on("networkEvent", this._onNetworkEvent);
|
||||
this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
this._displayCachedEvents();
|
||||
},
|
||||
|
||||
/**
|
||||
* Display any network events already in the cache.
|
||||
*/
|
||||
_displayCachedEvents: function() {
|
||||
for (let cachedEvent of this.webConsoleClient.getNetworkEvents()) {
|
||||
// First add the request to the timeline.
|
||||
this._onNetworkEvent("networkEvent", cachedEvent);
|
||||
// Then replay any updates already received.
|
||||
for (let update of cachedEvent.updates) {
|
||||
this._onNetworkEventUpdate("networkEventUpdate", {
|
||||
packet: {
|
||||
updateType: update
|
||||
},
|
||||
networkInfo: cachedEvent
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -540,25 +488,21 @@ NetworkEventsHandler.prototype = {
|
||||
return;
|
||||
}
|
||||
dumpn("NetworkEventsHandler is disconnecting...");
|
||||
this.client.removeListener("networkEvent", this._onNetworkEvent);
|
||||
this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
this.webConsoleClient.off("networkEvent", this._onNetworkEvent);
|
||||
this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
},
|
||||
|
||||
/**
|
||||
* The "networkEvent" message type handler.
|
||||
*
|
||||
* @param string aType
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object aPacket
|
||||
* The message received from the server.
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
*/
|
||||
_onNetworkEvent: function(aType, aPacket) {
|
||||
if (aPacket.from != this.webConsoleClient.actor) {
|
||||
// Skip events from different console actors.
|
||||
return;
|
||||
}
|
||||
_onNetworkEvent: function(type, networkInfo) {
|
||||
let { actor, startedDateTime, request: { method, url }, isXHR, fromCache } = networkInfo;
|
||||
|
||||
let { actor, startedDateTime, method, url, isXHR, fromCache } = aPacket.eventActor;
|
||||
NetMonitorView.RequestsMenu.addRequest(
|
||||
actor, startedDateTime, method, url, isXHR, fromCache
|
||||
);
|
||||
@ -568,19 +512,17 @@ NetworkEventsHandler.prototype = {
|
||||
/**
|
||||
* The "networkEventUpdate" message type handler.
|
||||
*
|
||||
* @param string aType
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object aPacket
|
||||
* @param object packet
|
||||
* The message received from the server.
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
*/
|
||||
_onNetworkEventUpdate: function(aType, aPacket) {
|
||||
let actor = aPacket.from;
|
||||
if (!NetMonitorView.RequestsMenu.getItemByValue(actor)) {
|
||||
// Skip events from unknown actors.
|
||||
return;
|
||||
}
|
||||
_onNetworkEventUpdate: function(type, { packet, networkInfo }) {
|
||||
let actor = networkInfo.actor;
|
||||
|
||||
switch (aPacket.updateType) {
|
||||
switch (packet.updateType) {
|
||||
case "requestHeaders":
|
||||
this.webConsoleClient.getRequestHeaders(actor, this._onRequestHeaders);
|
||||
window.emit(EVENTS.UPDATING_REQUEST_HEADERS, actor);
|
||||
@ -594,8 +536,8 @@ NetworkEventsHandler.prototype = {
|
||||
window.emit(EVENTS.UPDATING_REQUEST_POST_DATA, actor);
|
||||
break;
|
||||
case "securityInfo":
|
||||
NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
|
||||
securityState: aPacket.state,
|
||||
NetMonitorView.RequestsMenu.updateRequest(actor, {
|
||||
securityState: networkInfo.securityInfo,
|
||||
});
|
||||
this.webConsoleClient.getSecurityInfo(actor, this._onSecurityInfo);
|
||||
window.emit(EVENTS.UPDATING_SECURITY_INFO, actor);
|
||||
@ -609,28 +551,28 @@ NetworkEventsHandler.prototype = {
|
||||
window.emit(EVENTS.UPDATING_RESPONSE_COOKIES, actor);
|
||||
break;
|
||||
case "responseStart":
|
||||
NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
|
||||
httpVersion: aPacket.response.httpVersion,
|
||||
remoteAddress: aPacket.response.remoteAddress,
|
||||
remotePort: aPacket.response.remotePort,
|
||||
status: aPacket.response.status,
|
||||
statusText: aPacket.response.statusText,
|
||||
headersSize: aPacket.response.headersSize
|
||||
NetMonitorView.RequestsMenu.updateRequest(actor, {
|
||||
httpVersion: networkInfo.response.httpVersion,
|
||||
remoteAddress: networkInfo.response.remoteAddress,
|
||||
remotePort: networkInfo.response.remotePort,
|
||||
status: networkInfo.response.status,
|
||||
statusText: networkInfo.response.statusText,
|
||||
headersSize: networkInfo.response.headersSize
|
||||
});
|
||||
window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
|
||||
break;
|
||||
case "responseContent":
|
||||
NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
|
||||
contentSize: aPacket.contentSize,
|
||||
transferredSize: aPacket.transferredSize,
|
||||
mimeType: aPacket.mimeType
|
||||
NetMonitorView.RequestsMenu.updateRequest(actor, {
|
||||
contentSize: networkInfo.response.bodySize,
|
||||
transferredSize: networkInfo.response.transferredSize,
|
||||
mimeType: networkInfo.response.content.mimeType
|
||||
});
|
||||
this.webConsoleClient.getResponseContent(actor, this._onResponseContent);
|
||||
window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
|
||||
break;
|
||||
case "eventTimings":
|
||||
NetMonitorView.RequestsMenu.updateRequest(aPacket.from, {
|
||||
totalTime: aPacket.totalTime
|
||||
NetMonitorView.RequestsMenu.updateRequest(actor, {
|
||||
totalTime: networkInfo.totalTime
|
||||
});
|
||||
this.webConsoleClient.getEventTimings(actor, this._onEventTimings);
|
||||
window.emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
|
||||
|
@ -15,9 +15,6 @@ function test() {
|
||||
let { RequestsMenu } = NetMonitorView;
|
||||
reqMenu = RequestsMenu;
|
||||
|
||||
is(reqMenu.itemCount, 0,
|
||||
"The request menu should empty before reloading");
|
||||
|
||||
let button = document.querySelector("#requests-menu-reload-notice-button");
|
||||
button.click();
|
||||
})
|
||||
|
@ -147,6 +147,11 @@ function initNetMonitor(aUrl, aWindow, aEnableCache) {
|
||||
if(!aEnableCache) {
|
||||
yield toggleCache(target, true);
|
||||
info("Cache disabled when the current and all future toolboxes are open.");
|
||||
// Remove any requests generated by the reload while toggling the cache to
|
||||
// avoid interfering with the test.
|
||||
isnot([...target.activeConsole.getNetworkEvents()].length, 0,
|
||||
"Request to reconfigure the tab was recorded.");
|
||||
target.activeConsole.clearNetworkRequests();
|
||||
}
|
||||
|
||||
let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
|
||||
|
@ -201,11 +201,13 @@ function buggyDragStop(graph, x, y = 1) {
|
||||
x /= window.devicePixelRatio;
|
||||
y /= window.devicePixelRatio;
|
||||
|
||||
// Only fire a mousemove instead of a mouseup.
|
||||
// This happens when the mouseup happens outside of the toolbox,
|
||||
// see Bug 1066504.
|
||||
graph._onMouseMove({ testX: x, testY: y });
|
||||
graph._onMouseMove({ testX: x, testY: y, buttons: 0 });
|
||||
|
||||
// Only fire a mousemove with no buttons instead of a mouseup.
|
||||
// This happens when the mouseup happens outside of the window.
|
||||
// Send different coordinates to make sure the selection is preserved,
|
||||
// see Bugs 1066504 and 1144779.
|
||||
graph._onMouseMove({ testX: x+1, testY: y+1, buttons: 0 });
|
||||
}
|
||||
|
||||
function scroll(graph, wheel, x, y = 1) {
|
||||
|
@ -986,12 +986,12 @@ AbstractCanvasGraph.prototype = {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
// If a mouseup happened outside the toolbox and the current operation
|
||||
// is causing the selection changed, then end it.
|
||||
// If a mouseup happened outside the window and the current operation
|
||||
// is causing the selection to change, then end it.
|
||||
if (e.buttons == 0 && (this.hasSelectionInProgress() ||
|
||||
resizer.margin != null ||
|
||||
dragger.origin != null)) {
|
||||
return this._onMouseUp(e);
|
||||
return this._onMouseUp();
|
||||
}
|
||||
|
||||
let {mouseX,mouseY} = this._getRelativeEventCoordinates(e);
|
||||
@ -1093,10 +1093,8 @@ AbstractCanvasGraph.prototype = {
|
||||
/**
|
||||
* Listener for the "mouseup" event on the graph's container.
|
||||
*/
|
||||
_onMouseUp: function(e) {
|
||||
_onMouseUp: function() {
|
||||
this._isMouseActive = false;
|
||||
let {mouseX} = this._getRelativeEventCoordinates(e);
|
||||
|
||||
switch (this._canvas.getAttribute("input")) {
|
||||
case "hovering-background":
|
||||
case "hovering-region":
|
||||
@ -1115,7 +1113,7 @@ AbstractCanvasGraph.prototype = {
|
||||
this.emit("deselecting");
|
||||
}
|
||||
} else {
|
||||
this._selection.end = mouseX;
|
||||
this._selection.end = this._cursor.x;
|
||||
this.emit("selecting");
|
||||
}
|
||||
break;
|
||||
|
@ -385,3 +385,5 @@ skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
|
||||
[browser_webconsole_column_numbers.js]
|
||||
[browser_console_open_or_focus.js]
|
||||
[browser_webconsole_bug_922212_console_dirxml.js]
|
||||
[browser_webconsole_shows_reqs_in_netmonitor.js]
|
||||
[browser_netmonitor_shows_reqs_in_webconsole.js]
|
||||
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf8,Test that the netmonitor " +
|
||||
"displays requests that have been recorded in the " +
|
||||
"web console, even if the netmonitor hadn't opened yet.";
|
||||
|
||||
const TEST_FILE = "test-network-request.html";
|
||||
const TEST_PATH = "http://example.com/browser/browser/devtools/webconsole/test/" +
|
||||
TEST_FILE;
|
||||
|
||||
const NET_PREF = "devtools.webconsole.filter.networkinfo";
|
||||
Services.prefs.setBoolPref(NET_PREF, true);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(NET_PREF);
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, browser } = yield loadTab(TEST_URI);
|
||||
|
||||
// Test that the request appears in the console.
|
||||
let hud = yield openConsole();
|
||||
info("Web console is open");
|
||||
|
||||
yield loadDocument(browser);
|
||||
info("Document loaded.");
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
name: "network message",
|
||||
text: TEST_FILE,
|
||||
category: CATEGORY_NETWORK,
|
||||
severity: SEVERITY_LOG
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// Test that the request appears in the network panel.
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
|
||||
info("Network panel is open.");
|
||||
|
||||
testNetmonitor(toolbox);
|
||||
});
|
||||
|
||||
|
||||
function loadDocument(browser) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
deferred.resolve();
|
||||
}, true);
|
||||
content.location = TEST_PATH;
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testNetmonitor(toolbox) {
|
||||
let monitor = toolbox.getCurrentPanel();
|
||||
let { RequestsMenu } = monitor.panelWin.NetMonitorView;
|
||||
is(RequestsMenu.itemCount, 1, "Network request appears in the network panel");
|
||||
|
||||
let item = RequestsMenu.getItemAtIndex(0);
|
||||
is(item.attachment.method, "GET", "The attached method is correct.");
|
||||
is(item.attachment.url, TEST_PATH, "The attached url is correct.");
|
||||
}
|
@ -73,7 +73,8 @@ function testPageLoad()
|
||||
ok(!lastRequest.request.postData.text, "No request body was stored");
|
||||
ok(lastRequest.discardRequestBody, "Request body was discarded");
|
||||
ok(!lastRequest.response.content.text, "No response body was stored");
|
||||
ok(lastRequest.discardResponseBody, "Response body was discarded");
|
||||
ok(lastRequest.discardResponseBody || lastRequest.fromCache,
|
||||
"Response body was discarded or response came from the cache");
|
||||
|
||||
lastRequest = null;
|
||||
requestCallback = null;
|
||||
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf8,Test that the web console " +
|
||||
"displays requests that have been recorded in the " +
|
||||
"netmonitor, even if the console hadn't opened yet.";
|
||||
|
||||
const TEST_FILE = "test-network-request.html";
|
||||
const TEST_PATH = "http://example.com/browser/browser/devtools/webconsole/test/" +
|
||||
TEST_FILE;
|
||||
|
||||
const NET_PREF = "devtools.webconsole.filter.networkinfo";
|
||||
Services.prefs.setBoolPref(NET_PREF, true);
|
||||
registerCleanupFunction(() => {
|
||||
Services.prefs.clearUserPref(NET_PREF);
|
||||
});
|
||||
|
||||
add_task(function* () {
|
||||
let { tab, browser } = yield loadTab(TEST_URI);
|
||||
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
|
||||
info("Network panel is open.");
|
||||
|
||||
yield loadDocument(browser);
|
||||
info("Document loaded.");
|
||||
|
||||
// Test that the request appears in the network panel.
|
||||
testNetmonitor(toolbox);
|
||||
|
||||
// Test that the request appears in the console.
|
||||
let hud = yield openConsole();
|
||||
info("Web console is open");
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
name: "network message",
|
||||
text: TEST_FILE,
|
||||
category: CATEGORY_NETWORK,
|
||||
severity: SEVERITY_LOG
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function loadDocument(browser) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
deferred.resolve();
|
||||
}, true);
|
||||
content.location = TEST_PATH;
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function testNetmonitor(toolbox) {
|
||||
let monitor = toolbox.getCurrentPanel();
|
||||
let { RequestsMenu } = monitor.panelWin.NetMonitorView;
|
||||
is(RequestsMenu.itemCount, 1, "Network request appears in the network panel");
|
||||
|
||||
let item = RequestsMenu.getItemAtIndex(0);
|
||||
is(item.attachment.method, "GET", "The attached method is correct.");
|
||||
is(item.attachment.url, TEST_PATH, "The attached url is correct.");
|
||||
}
|
@ -15,7 +15,6 @@ let {Utils: WebConsoleUtils} = require("devtools/toolkit/webconsole/utils");
|
||||
let {Messages} = require("devtools/webconsole/console-output");
|
||||
const asyncStorage = require("devtools/toolkit/shared/async-storage");
|
||||
|
||||
// promise._reportErrors = true; // please never leave me.
|
||||
//Services.prefs.setBoolPref("devtools.debugger.log", true);
|
||||
|
||||
let gPendingOutputTest = 0;
|
||||
|
@ -205,7 +205,6 @@ function WebConsoleFrame(aWebConsoleOwner)
|
||||
this._outputQueue = [];
|
||||
this._itemDestroyQueue = [];
|
||||
this._pruneCategoriesQueue = {};
|
||||
this._networkRequests = {};
|
||||
this.filterPrefs = {};
|
||||
|
||||
this.output = new ConsoleOutput(this);
|
||||
@ -253,14 +252,6 @@ WebConsoleFrame.prototype = {
|
||||
*/
|
||||
_initDefer: null,
|
||||
|
||||
/**
|
||||
* Holds the network requests currently displayed by the Web Console. Each key
|
||||
* represents the connection ID and the value is network request information.
|
||||
* @private
|
||||
* @type object
|
||||
*/
|
||||
_networkRequests: null,
|
||||
|
||||
/**
|
||||
* Last time when we displayed any message in the output.
|
||||
*
|
||||
@ -1222,6 +1213,9 @@ WebConsoleFrame.prototype = {
|
||||
this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage,
|
||||
[aMessage]);
|
||||
break;
|
||||
case "NetworkEvent":
|
||||
this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [aMessage]);
|
||||
break;
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
@ -1528,19 +1522,14 @@ WebConsoleFrame.prototype = {
|
||||
/**
|
||||
* Log network event.
|
||||
*
|
||||
* @param object aActor
|
||||
* The network event actor to log.
|
||||
* @param object networkInfo
|
||||
* The network request information to log.
|
||||
* @return nsIDOMElement|null
|
||||
* The message element to display in the Web Console output.
|
||||
*/
|
||||
logNetEvent: function WCF_logNetEvent(aActor)
|
||||
logNetEvent: function(networkInfo)
|
||||
{
|
||||
let actorId = aActor.actor;
|
||||
let networkInfo = this._networkRequests[actorId];
|
||||
if (!networkInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let actorId = networkInfo.actor;
|
||||
let request = networkInfo.request;
|
||||
let clipboardText = request.method + " " + request.url;
|
||||
let severity = SEVERITY_LOG;
|
||||
@ -1560,7 +1549,8 @@ WebConsoleFrame.prototype = {
|
||||
|
||||
let messageNode = this.createMessageNode(CATEGORY_NETWORK, severity,
|
||||
methodNode, null, null,
|
||||
clipboardText);
|
||||
clipboardText, null,
|
||||
networkInfo.timeStamp);
|
||||
if (networkInfo.private) {
|
||||
messageNode.setAttribute("private", true);
|
||||
}
|
||||
@ -1798,84 +1788,29 @@ WebConsoleFrame.prototype = {
|
||||
/**
|
||||
* Handle the network events coming from the remote Web Console.
|
||||
*
|
||||
* @param object aActor
|
||||
* The NetworkEventActor grip.
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
*/
|
||||
handleNetworkEvent: function WCF_handleNetworkEvent(aActor)
|
||||
handleNetworkEvent: function(networkInfo)
|
||||
{
|
||||
let networkInfo = {
|
||||
node: null,
|
||||
actor: aActor.actor,
|
||||
discardRequestBody: true,
|
||||
discardResponseBody: true,
|
||||
startedDateTime: aActor.startedDateTime,
|
||||
request: {
|
||||
url: aActor.url,
|
||||
method: aActor.method,
|
||||
},
|
||||
isXHR: aActor.isXHR,
|
||||
response: {},
|
||||
timings: {},
|
||||
updates: [], // track the list of network event updates
|
||||
private: aActor.private,
|
||||
};
|
||||
|
||||
this._networkRequests[aActor.actor] = networkInfo;
|
||||
this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [aActor]);
|
||||
this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [networkInfo]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle network event updates coming from the server.
|
||||
*
|
||||
* @param string aActorId
|
||||
* The network event actor ID.
|
||||
* @param string aType
|
||||
* Update type.
|
||||
* @param object aPacket
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
* @param object packet
|
||||
* Update details.
|
||||
*/
|
||||
handleNetworkEventUpdate:
|
||||
function WCF_handleNetworkEventUpdate(aActorId, aType, aPacket)
|
||||
handleNetworkEventUpdate: function(networkInfo, packet)
|
||||
{
|
||||
let networkInfo = this._networkRequests[aActorId];
|
||||
if (!networkInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
networkInfo.updates.push(aType);
|
||||
|
||||
switch (aType) {
|
||||
case "requestHeaders":
|
||||
networkInfo.request.headersSize = aPacket.headersSize;
|
||||
break;
|
||||
case "requestPostData":
|
||||
networkInfo.discardRequestBody = aPacket.discardRequestBody;
|
||||
networkInfo.request.bodySize = aPacket.dataSize;
|
||||
break;
|
||||
case "responseStart":
|
||||
networkInfo.response.httpVersion = aPacket.response.httpVersion;
|
||||
networkInfo.response.status = aPacket.response.status;
|
||||
networkInfo.response.statusText = aPacket.response.statusText;
|
||||
networkInfo.response.headersSize = aPacket.response.headersSize;
|
||||
networkInfo.discardResponseBody = aPacket.response.discardResponseBody;
|
||||
break;
|
||||
case "responseContent":
|
||||
networkInfo.response.content = {
|
||||
mimeType: aPacket.mimeType,
|
||||
};
|
||||
networkInfo.response.bodySize = aPacket.contentSize;
|
||||
networkInfo.discardResponseBody = aPacket.discardResponseBody;
|
||||
break;
|
||||
case "eventTimings":
|
||||
networkInfo.totalTime = aPacket.totalTime;
|
||||
break;
|
||||
}
|
||||
|
||||
if (networkInfo.node && this._updateNetMessage(aActorId)) {
|
||||
if (networkInfo.node && this._updateNetMessage(packet.from)) {
|
||||
this.emit("new-messages", new Set([{
|
||||
update: true,
|
||||
node: networkInfo.node,
|
||||
response: aPacket,
|
||||
response: packet,
|
||||
}]));
|
||||
}
|
||||
|
||||
@ -1900,7 +1835,7 @@ WebConsoleFrame.prototype = {
|
||||
*/
|
||||
_updateNetMessage: function WCF__updateNetMessage(aActorId)
|
||||
{
|
||||
let networkInfo = this._networkRequests[aActorId];
|
||||
let networkInfo = this.webConsoleClient.getNetworkRequest(aActorId);
|
||||
if (!networkInfo || !networkInfo.node) {
|
||||
return;
|
||||
}
|
||||
@ -2446,8 +2381,8 @@ WebConsoleFrame.prototype = {
|
||||
else if (typeof methodOrNode != "function") {
|
||||
connectionId = methodOrNode._connectionId;
|
||||
}
|
||||
if (connectionId && connectionId in this._networkRequests) {
|
||||
delete this._networkRequests[connectionId];
|
||||
if (connectionId && this.webConsoleClient.hasNetworkRequest(connectionId)) {
|
||||
this.webConsoleClient.removeNetworkRequest(connectionId);
|
||||
this._releaseObject(connectionId);
|
||||
}
|
||||
}
|
||||
@ -2524,7 +2459,7 @@ WebConsoleFrame.prototype = {
|
||||
}
|
||||
else if (aNode._connectionId &&
|
||||
aNode.category == CATEGORY_NETWORK) {
|
||||
delete this._networkRequests[aNode._connectionId];
|
||||
this.webConsoleClient.removeNetworkRequest(aNode._connectionId);
|
||||
this._releaseObject(aNode._connectionId);
|
||||
}
|
||||
else if (aNode.classList.contains("inlined-variables-view")) {
|
||||
@ -3018,7 +2953,7 @@ WebConsoleFrame.prototype = {
|
||||
this._itemDestroyQueue.forEach(this._destroyItem, this);
|
||||
this._itemDestroyQueue = [];
|
||||
this._pruneCategoriesQueue = {};
|
||||
this._networkRequests = {};
|
||||
this.webConsoleClient.clearNetworkRequests();
|
||||
|
||||
if (this._outputTimerInitialized) {
|
||||
this._outputTimerInitialized = false;
|
||||
@ -3972,7 +3907,7 @@ JSTerm.prototype = {
|
||||
hud.groupDepth = 0;
|
||||
hud._outputQueue.forEach(hud._destroyItem, hud);
|
||||
hud._outputQueue = [];
|
||||
hud._networkRequests = {};
|
||||
this.webConsoleClient.clearNetworkRequests();
|
||||
hud._repeatNodes = {};
|
||||
|
||||
if (aClearStorage) {
|
||||
@ -5114,8 +5049,6 @@ WebConsoleConnectionProxy.prototype = {
|
||||
client.addListener("logMessage", this._onLogMessage);
|
||||
client.addListener("pageError", this._onPageError);
|
||||
client.addListener("consoleAPICall", this._onConsoleAPICall);
|
||||
client.addListener("networkEvent", this._onNetworkEvent);
|
||||
client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
client.addListener("fileActivity", this._onFileActivity);
|
||||
client.addListener("reflowActivity", this._onReflowActivity);
|
||||
client.addListener("lastPrivateContextExited", this._onLastPrivateContextExited);
|
||||
@ -5180,6 +5113,8 @@ WebConsoleConnectionProxy.prototype = {
|
||||
this.webConsoleClient = aWebConsoleClient;
|
||||
|
||||
this._hasNativeConsoleAPI = aResponse.nativeConsoleAPI;
|
||||
this.webConsoleClient.on("networkEvent", this._onNetworkEvent);
|
||||
this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
|
||||
let msgs = ["PageError", "ConsoleAPI"];
|
||||
this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages);
|
||||
@ -5209,7 +5144,10 @@ WebConsoleConnectionProxy.prototype = {
|
||||
Cu.reportError("Web Console getCachedMessages error: invalid state.");
|
||||
}
|
||||
|
||||
this.owner.displayCachedMessages(aResponse.messages);
|
||||
let messages = aResponse.messages.concat(...this.webConsoleClient.getNetworkEvents());
|
||||
messages.sort((a, b) => a.timeStamp - b.timeStamp);
|
||||
|
||||
this.owner.displayCachedMessages(messages);
|
||||
|
||||
if (!this._hasNativeConsoleAPI) {
|
||||
this.owner.logWarningAboutReplacedAPI();
|
||||
@ -5275,15 +5213,15 @@ WebConsoleConnectionProxy.prototype = {
|
||||
* the UI for displaying.
|
||||
*
|
||||
* @private
|
||||
* @param string aType
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object aPacket
|
||||
* The message received from the server.
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
*/
|
||||
_onNetworkEvent: function WCCP__onNetworkEvent(aType, aPacket)
|
||||
_onNetworkEvent: function(type, networkInfo)
|
||||
{
|
||||
if (this.owner && aPacket.from == this._consoleActor) {
|
||||
this.owner.handleNetworkEvent(aPacket.eventActor);
|
||||
if (this.owner) {
|
||||
this.owner.handleNetworkEvent(networkInfo);
|
||||
}
|
||||
},
|
||||
|
||||
@ -5292,16 +5230,17 @@ WebConsoleConnectionProxy.prototype = {
|
||||
* the UI for displaying.
|
||||
*
|
||||
* @private
|
||||
* @param string aType
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object aPacket
|
||||
* @param object packet
|
||||
* The message received from the server.
|
||||
* @param object networkInfo
|
||||
* The network request information.
|
||||
*/
|
||||
_onNetworkEventUpdate: function WCCP__onNetworkEvenUpdatet(aType, aPacket)
|
||||
_onNetworkEventUpdate: function(type, { packet, networkInfo })
|
||||
{
|
||||
if (this.owner) {
|
||||
this.owner.handleNetworkEventUpdate(aPacket.from, aPacket.updateType,
|
||||
aPacket);
|
||||
this.owner.handleNetworkEventUpdate(networkInfo, packet);
|
||||
}
|
||||
},
|
||||
|
||||
@ -5401,11 +5340,11 @@ WebConsoleConnectionProxy.prototype = {
|
||||
this.client.removeListener("logMessage", this._onLogMessage);
|
||||
this.client.removeListener("pageError", this._onPageError);
|
||||
this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
|
||||
this.client.removeListener("networkEvent", this._onNetworkEvent);
|
||||
this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
this.client.removeListener("fileActivity", this._onFileActivity);
|
||||
this.client.removeListener("reflowActivity", this._onReflowActivity);
|
||||
this.client.removeListener("lastPrivateContextExited", this._onLastPrivateContextExited);
|
||||
this.webConsoleClient.off("networkEvent", this._onNetworkEvent);
|
||||
this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate);
|
||||
this.target.off("will-navigate", this._onTabNavigated);
|
||||
this.target.off("navigate", this._onTabNavigated);
|
||||
|
||||
|
@ -254,10 +254,7 @@ Section "-InstallStartCleanup"
|
||||
${CleanUpdateDirectories} "Mozilla\Firefox" "Mozilla\updates"
|
||||
|
||||
${RemoveDeprecatedFiles}
|
||||
|
||||
StrCpy $R2 "false"
|
||||
StrCpy $R3 "false"
|
||||
${RemovePrecompleteEntries} "$R2" "$R3"
|
||||
${RemovePrecompleteEntries} "false"
|
||||
|
||||
${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js"
|
||||
Delete "$INSTDIR\defaults\pref\channel-prefs.js"
|
||||
|
@ -71,6 +71,8 @@ Var CanSetAsDefault
|
||||
Var InstallCounterStep
|
||||
Var InstallStepSize
|
||||
Var InstallTotalSteps
|
||||
Var ProgressCompleted
|
||||
Var ProgressTotal
|
||||
Var TmpVal
|
||||
|
||||
Var ExitCode
|
||||
@ -258,6 +260,7 @@ Var ControlRightPX
|
||||
!insertmacro IsUserAdmin
|
||||
!insertmacro RemovePrecompleteEntries
|
||||
!insertmacro SetBrandNameVars
|
||||
!insertmacro ITBL3Create
|
||||
!insertmacro UnloadUAC
|
||||
|
||||
VIAddVersionKey "FileDescription" "${BrandShortName} Stub Installer"
|
||||
@ -1293,6 +1296,9 @@ Function createInstall
|
||||
StrCpy $InstallTotalSteps ${InstallCleanTotalSteps}
|
||||
${EndIf}
|
||||
|
||||
${ITBL3Create}
|
||||
${ITBL3SetProgressState} "${TBPF_INDETERMINATE}"
|
||||
|
||||
${NSD_CreateTimer} StartDownload ${DownloadIntervalMS}
|
||||
|
||||
LockWindow off
|
||||
@ -1315,6 +1321,21 @@ Function StartDownload
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function SetProgressBars
|
||||
SendMessage $Progressbar ${PBM_SETPOS} $ProgressCompleted 0
|
||||
${ITBL3SetProgressValue} "$ProgressCompleted" "$ProgressTotal"
|
||||
FunctionEnd
|
||||
|
||||
Function RemoveFileProgressCallback
|
||||
IntOp $InstallCounterStep $InstallCounterStep + 2
|
||||
System::Int64Op $ProgressCompleted + $InstallStepSize
|
||||
Pop $ProgressCompleted
|
||||
Call SetProgressBars
|
||||
System::Int64Op $ProgressCompleted + $InstallStepSize
|
||||
Pop $ProgressCompleted
|
||||
Call SetProgressBars
|
||||
FunctionEnd
|
||||
|
||||
Function OnDownload
|
||||
InetBgDL::GetStats
|
||||
# $0 = HTTP status code, 0=Completed
|
||||
@ -1333,6 +1354,7 @@ Function OnDownload
|
||||
${NSD_AddStyle} $Progressbar ${PBS_MARQUEE}
|
||||
SendMessage $Progressbar ${PBM_SETMARQUEE} 1 \
|
||||
$ProgressbarMarqueeIntervalMS ; start=1|stop=0 interval(ms)=+N
|
||||
${ITBL3SetProgressState} "${TBPF_INDETERMINATE}"
|
||||
${EndIf}
|
||||
InetBgDL::Get /RESET /END
|
||||
StrCpy $DownloadSizeBytes ""
|
||||
@ -1390,8 +1412,9 @@ Function OnDownload
|
||||
SendMessage $Progressbar ${PBM_SETMARQUEE} 0 0 ; start=1|stop=0 interval(ms)=+N
|
||||
${RemoveStyle} $Progressbar ${PBS_MARQUEE}
|
||||
System::Int64Op $HalfOfDownload + $DownloadSizeBytes
|
||||
Pop $R9
|
||||
SendMessage $Progressbar ${PBM_SETRANGE32} 0 $R9
|
||||
Pop $ProgressTotal
|
||||
StrCpy $ProgressCompleted 0
|
||||
SendMessage $Progressbar ${PBM_SETRANGE32} $ProgressCompleted $ProgressTotal
|
||||
${EndIf}
|
||||
|
||||
; Don't update the status until after the download starts
|
||||
@ -1448,12 +1471,13 @@ Function OnDownload
|
||||
LockWindow on
|
||||
; Update the progress bars first in the UI change so they take affect
|
||||
; before other UI changes.
|
||||
SendMessage $Progressbar ${PBM_SETPOS} $DownloadSizeBytes 0
|
||||
StrCpy $ProgressCompleted "$DownloadSizeBytes"
|
||||
Call SetProgressBars
|
||||
System::Int64Op $InstallStepSize * ${InstallProgressFirstStep}
|
||||
Pop $R9
|
||||
SendMessage $Progressbar ${PBM_SETSTEP} $R9 0
|
||||
SendMessage $Progressbar ${PBM_STEPIT} 0 0
|
||||
SendMessage $Progressbar ${PBM_SETSTEP} $InstallStepSize 0
|
||||
System::Int64Op $ProgressCompleted + $R9
|
||||
Pop $ProgressCompleted
|
||||
Call SetProgressBars
|
||||
ShowWindow $LabelDownloading ${SW_HIDE}
|
||||
ShowWindow $LabelInstalling ${SW_SHOW}
|
||||
ShowWindow $LabelBlurb2 ${SW_HIDE}
|
||||
@ -1540,7 +1564,8 @@ Function OnDownload
|
||||
WriteIniStr "$0" "TASKBAR" "Migrated" "true"
|
||||
${EndIf}
|
||||
|
||||
${RemovePrecompleteEntries} $Progressbar $InstallCounterStep
|
||||
GetFunctionAddress $0 RemoveFileProgressCallback
|
||||
${RemovePrecompleteEntries} $0
|
||||
|
||||
; Delete the install.log and let the full installer create it. When the
|
||||
; installer closes it we can detect that it has completed.
|
||||
@ -1569,7 +1594,8 @@ Function OnDownload
|
||||
LockWindow off
|
||||
${EndIf}
|
||||
StrCpy $DownloadedBytes "$3"
|
||||
SendMessage $Progressbar ${PBM_SETPOS} $3 0
|
||||
StrCpy $ProgressCompleted "$DownloadedBytes"
|
||||
Call SetProgressBars
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
@ -1608,7 +1634,9 @@ Function CheckInstall
|
||||
Return
|
||||
${EndIf}
|
||||
|
||||
SendMessage $Progressbar ${PBM_STEPIT} 0 0
|
||||
System::Int64Op $ProgressCompleted + $InstallStepSize
|
||||
Pop $ProgressCompleted
|
||||
Call SetProgressBars
|
||||
|
||||
${If} ${FileExists} "$INSTDIR\install.log"
|
||||
Delete "$INSTDIR\install.tmp"
|
||||
@ -1632,7 +1660,6 @@ Function CheckInstall
|
||||
Pop $EndInstallPhaseTickCount
|
||||
System::Int64Op $InstallStepSize * ${InstallProgressFinishStep}
|
||||
Pop $InstallStepSize
|
||||
SendMessage $Progressbar ${PBM_SETSTEP} $InstallStepSize 0
|
||||
${NSD_CreateTimer} FinishInstall ${InstallIntervalMS}
|
||||
${EndUnless}
|
||||
${EndIf}
|
||||
@ -1647,14 +1674,16 @@ Function FinishInstall
|
||||
${EndIf}
|
||||
|
||||
${If} $InstallTotalSteps != $InstallCounterStep
|
||||
SendMessage $Progressbar ${PBM_STEPIT} 0 0
|
||||
System::Int64Op $ProgressCompleted + $InstallStepSize
|
||||
Pop $ProgressCompleted
|
||||
Call SetProgressBars
|
||||
Return
|
||||
${EndIf}
|
||||
|
||||
${NSD_KillTimer} FinishInstall
|
||||
|
||||
SendMessage $Progressbar ${PBM_GETRANGE} 0 0 $R9
|
||||
SendMessage $Progressbar ${PBM_SETPOS} $R9 0
|
||||
StrCpy $ProgressCompleted "$ProgressTotal"
|
||||
Call SetProgressBars
|
||||
|
||||
${If} "$CheckboxSetAsDefault" == "1"
|
||||
${GetParameters} $0
|
||||
@ -1935,6 +1964,10 @@ FunctionEnd
|
||||
|
||||
Function DisplayDownloadError
|
||||
${NSD_KillTimer} DisplayDownloadError
|
||||
; To better display the error state on the taskbar set the progress completed
|
||||
; value to the total value.
|
||||
${ITBL3SetProgressValue} "100" "100"
|
||||
${ITBL3SetProgressState} "${TBPF_ERROR}"
|
||||
MessageBox MB_OKCANCEL|MB_ICONSTOP "$(ERROR_DOWNLOAD)" IDCANCEL +2 IDOK +1
|
||||
StrCpy $OpenedDownloadPage "1" ; Already initialized to 0
|
||||
|
||||
|
@ -397,9 +397,7 @@ Section "Uninstall"
|
||||
${UnregisterDLL} "$INSTDIR\AccessibleMarshal.dll"
|
||||
${EndIf}
|
||||
|
||||
StrCpy $R2 "false"
|
||||
StrCpy $R3 "false"
|
||||
${un.RemovePrecompleteEntries} "$R2" "$R3"
|
||||
${un.RemovePrecompleteEntries} "false"
|
||||
|
||||
${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js"
|
||||
Delete /REBOOTOK "$INSTDIR\defaults\pref\channel-prefs.js"
|
||||
|
@ -37,17 +37,26 @@ xpinstallDisabledButton.accesskey=n
|
||||
addonDownloadingAndVerifying=Downloading and verifying add-on…;Downloading and verifying #1 add-ons…
|
||||
addonDownloadVerifying=Verifying
|
||||
|
||||
addonInstall.unsigned=(Unverified)
|
||||
addonInstall.cancelButton.label=Cancel
|
||||
addonInstall.cancelButton.accesskey=C
|
||||
addonInstall.acceptButton.label=Install
|
||||
addonInstall.acceptButton.accesskey=I
|
||||
|
||||
# LOCALIZATION NOTE (addonConfirmInstallMessage):
|
||||
# LOCALIZATION NOTE (addonConfirmInstallMessage,addonConfirmInstallUnsigned):
|
||||
# Semicolon-separated list of plural forms. See:
|
||||
# http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# #1 is brandShortName
|
||||
# #2 is the number of add-ons being installed
|
||||
addonConfirmInstall.message=This site would like to install an add-on in #1:;This site would like to install #2 add-ons in #1:
|
||||
addonConfirmInstallUnsigned.message=Caution: This site would like to install an unverified add-on in #1. Proceed at your own risk.;Caution: This site would like to install #2 unverified add-ons in #1. Proceed at your own risk.
|
||||
|
||||
# LOCALIZATION NOTE (addonConfirmInstallSomeUnsigned.message):
|
||||
# Semicolon-separated list of plural forms. See:
|
||||
# http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# #1 is brandShortName
|
||||
# #2 is the total number of add-ons being installed (at least 2)
|
||||
addonConfirmInstallSomeUnsigned.message=;Caution: This site would like to install #2 add-ons in #1, some of which are unverified. Proceed at your own risk.
|
||||
|
||||
addonwatch.slow=%1$S might be making %2$S run slowly
|
||||
addonwatch.disable.label=Disable %S
|
||||
|
@ -340,3 +340,7 @@ context_edit_activate_label=Talk about "{{title}}"
|
||||
context_edit_name_placeholder=Conversation Name
|
||||
context_edit_comments_placeholder=Comments
|
||||
context_add_some_label=Add some context
|
||||
context_edit_tooltip=Edit Context
|
||||
context_hide_tooltip=Hide Context
|
||||
context_show_tooltip=Show Context
|
||||
context_save_label=Save Context
|
||||
|
@ -1862,6 +1862,7 @@ toolbarbutton.chevron > .toolbarbutton-icon {
|
||||
%include ../shared/devtools/commandline.inc.css
|
||||
%include ../shared/plugin-doorhanger.inc.css
|
||||
%include ../shared/badcontent-doorhanger.inc.css
|
||||
%include ../shared/login-doorhanger.inc.css
|
||||
|
||||
%include downloads/indicator.css
|
||||
|
||||
|
@ -1,11 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
@import url("chrome://global/skin/inContentUI.css");
|
||||
|
||||
#downloadsListEmptyDescription {
|
||||
margin: 1em;
|
||||
text-align: center;
|
||||
color: GrayText;
|
||||
}
|
@ -134,7 +134,7 @@ browser.jar:
|
||||
skin/classic/browser/customizableui/whimsy-bw@2x.png (../shared/customizableui/whimsy-bw@2x.png)
|
||||
skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
|
||||
skin/classic/browser/downloads/buttons.png (downloads/buttons.png)
|
||||
skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
|
||||
skin/classic/browser/downloads/contentAreaDownloadsView.css (../shared/downloads/contentAreaDownloadsView.css)
|
||||
skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
|
||||
skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
|
||||
skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
|
||||
|
@ -3895,6 +3895,7 @@ notification[value="loop-sharing-notification"] .messageImage {
|
||||
%include ../shared/devtools/commandline.inc.css
|
||||
%include ../shared/plugin-doorhanger.inc.css
|
||||
%include ../shared/badcontent-doorhanger.inc.css
|
||||
%include ../shared/login-doorhanger.inc.css
|
||||
|
||||
%include downloads/indicator.css
|
||||
|
||||
|
@ -215,7 +215,7 @@ browser.jar:
|
||||
skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
|
||||
skin/classic/browser/downloads/buttons.png (downloads/buttons.png)
|
||||
skin/classic/browser/downloads/buttons@2x.png (downloads/buttons@2x.png)
|
||||
skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
|
||||
skin/classic/browser/downloads/contentAreaDownloadsView.css (../shared/downloads/contentAreaDownloadsView.css)
|
||||
skin/classic/browser/downloads/download-glow-menuPanel.png (downloads/download-glow-menuPanel.png)
|
||||
skin/classic/browser/downloads/download-glow-menuPanel@2x.png (downloads/download-glow-menuPanel@2x.png)
|
||||
skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
|
||||
|
@ -256,6 +256,11 @@ panelview:not([mainview]) .toolbarbutton-text,
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.cui-widget-panel[viewId="PanelUI-pocketView"] > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.cui-widget-panel.cui-widget-panelWithFooter > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
@ -2,17 +2,25 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
@import url("chrome://global/skin/inContentUI.css");
|
||||
@import url("chrome://global/skin/in-content/common.css");
|
||||
|
||||
.downloadButton {
|
||||
box-shadow: none;
|
||||
#contentAreaDownloadsView {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
#downloadsRichListBox:not(:-moz-focusring) {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.downloadButton:not([disabled="true"]):hover,
|
||||
.downloadButton:not([disabled="true"]):hover:active,
|
||||
.downloadButton:not([disabled]):hover:active {
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.downloadButton > .button-box {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#downloadsListEmptyDescription {
|
19
browser/themes/shared/login-doorhanger.inc.css
Normal file
19
browser/themes/shared/login-doorhanger.inc.css
Normal file
@ -0,0 +1,19 @@
|
||||
#login-fill-testing {
|
||||
color: #b33;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#login-fill-list {
|
||||
border: 1px solid black;
|
||||
max-height: 20em;
|
||||
}
|
||||
|
||||
.login-hostname {
|
||||
margin: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.login-username {
|
||||
margin: 4px;
|
||||
color: #888;
|
||||
}
|
@ -2456,6 +2456,7 @@ notification[value="loop-sharing-notification"] .messageImage {
|
||||
%include ../shared/devtools/commandline.inc.css
|
||||
%include ../shared/plugin-doorhanger.inc.css
|
||||
%include ../shared/badcontent-doorhanger.inc.css
|
||||
%include ../shared/login-doorhanger.inc.css
|
||||
|
||||
%include downloads/indicator.css
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
@import url("chrome://global/skin/inContentUI.css");
|
||||
|
||||
.downloadButton {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.downloadButton:not([disabled="true"]):hover:active,
|
||||
.downloadButton:not([disabled]):hover:active {
|
||||
background: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#downloadsListEmptyDescription {
|
||||
margin: 1em;
|
||||
text-align: center;
|
||||
color: GrayText;
|
||||
}
|
@ -173,7 +173,7 @@ browser.jar:
|
||||
skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
|
||||
skin/classic/browser/downloads/buttons.png (downloads/buttons.png)
|
||||
skin/classic/browser/downloads/buttons-XP.png (downloads/buttons-XP.png)
|
||||
skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
|
||||
skin/classic/browser/downloads/contentAreaDownloadsView.css (../shared/downloads/contentAreaDownloadsView.css)
|
||||
skin/classic/browser/downloads/download-glow-menuPanel-XPVista7.png (downloads/download-glow-menuPanel-XPVista7.png)
|
||||
skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
|
||||
skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
|
||||
|
@ -3519,10 +3519,10 @@ AC_SUBST(NIGHTLY_BUILD)
|
||||
AC_SUBST(RELEASE_BUILD)
|
||||
|
||||
dnl ========================================================
|
||||
dnl Multiprocess Firefox Nightly Testing UI
|
||||
dnl Multiprocess Firefox Testing UI - Nightly and Aurora
|
||||
dnl To be removed in Bug 1003313
|
||||
dnl ========================================================
|
||||
if test -n "$NIGHTLY_BUILD"; then
|
||||
if test -z "$RELEASE_BUILD"; then
|
||||
E10S_TESTING_ONLY=1
|
||||
AC_DEFINE(E10S_TESTING_ONLY)
|
||||
fi
|
||||
|
@ -2918,7 +2918,16 @@ ContentChild::RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers,
|
||||
} else if (item.data().type() == IPCDataTransferData::TPBlobChild) {
|
||||
BlobChild* blob = static_cast<BlobChild*>(item.data().get_PBlobChild());
|
||||
nsRefPtr<FileImpl> fileImpl = blob->GetBlobImpl();
|
||||
variant->SetAsISupports(fileImpl);
|
||||
nsString path;
|
||||
ErrorResult result;
|
||||
fileImpl->GetMozFullPathInternal(path, result);
|
||||
if (result.Failed()) {
|
||||
variant->SetAsISupports(fileImpl);
|
||||
} else {
|
||||
nsCOMPtr<nsIFile> file;
|
||||
NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(path), true, getter_AddRefs(file));
|
||||
variant->SetAsISupports(file);
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import com.nineoldandroids.animation.ValueAnimator;
|
||||
* After constructing an instance, animations can be queued up sequentially with the
|
||||
* {@link #queue(Attributes) queue} method.
|
||||
*/
|
||||
public class BounceAnimator extends ValueAnimator {
|
||||
public class BounceAnimatorBuilder extends ValueAnimator {
|
||||
|
||||
public static final class Attributes {
|
||||
public final float value;
|
||||
@ -34,7 +34,7 @@ public class BounceAnimator extends ValueAnimator {
|
||||
private final String mPropertyName;
|
||||
private final List<Animator> animatorChain = new LinkedList<Animator>();
|
||||
|
||||
public BounceAnimator(View view, String property) {
|
||||
public BounceAnimatorBuilder(View view, String property) {
|
||||
mView = view;
|
||||
mPropertyName = property;
|
||||
}
|
||||
@ -46,10 +46,9 @@ public class BounceAnimator extends ValueAnimator {
|
||||
animatorChain.add(animator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
public AnimatorSet build(){
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
animatorSet.playSequentially(animatorChain);
|
||||
animatorSet.start();
|
||||
return animatorSet;
|
||||
}
|
||||
}
|
@ -6,8 +6,8 @@
|
||||
package org.mozilla.gecko.home;
|
||||
|
||||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.animation.BounceAnimator;
|
||||
import org.mozilla.gecko.animation.BounceAnimator.Attributes;
|
||||
import org.mozilla.gecko.animation.BounceAnimatorBuilder;
|
||||
import org.mozilla.gecko.animation.BounceAnimatorBuilder.Attributes;
|
||||
import org.mozilla.gecko.animation.TransitionsTracker;
|
||||
|
||||
import android.content.Context;
|
||||
@ -33,12 +33,11 @@ import com.nineoldandroids.view.ViewHelper;
|
||||
class HomePagerTabStrip extends PagerTabStrip {
|
||||
|
||||
private static final String LOGTAG = "PagerTabStrip";
|
||||
private static final int ANIMATION_DELAY_MS = 250;
|
||||
private static final int ANIMATION_DELAY_MS = 50;
|
||||
private static final int ALPHA_MS = 10;
|
||||
private static final int BOUNCE1_MS = 350;
|
||||
private static final int BOUNCE2_MS = 200;
|
||||
private static final int BOUNCE3_MS = 100;
|
||||
private static final int BOUNCE4_MS = 100;
|
||||
private static final int INIT_OFFSET = 100;
|
||||
|
||||
private final Paint shadowPaint;
|
||||
@ -103,30 +102,33 @@ class HomePagerTabStrip extends PagerTabStrip {
|
||||
|
||||
final AnimatorSet alphaAnimatorSet = new AnimatorSet();
|
||||
alphaAnimatorSet.playTogether(alpha1, alpha2);
|
||||
alphaAnimatorSet.setStartDelay(ANIMATION_DELAY_MS);
|
||||
alphaAnimatorSet.setDuration(ALPHA_MS);
|
||||
alphaAnimatorSet.setStartDelay(ANIMATION_DELAY_MS);
|
||||
|
||||
// Bounce animation.
|
||||
final float bounceDistance = getWidth()/100f; // Hack: TextFields still have 0 width here.
|
||||
|
||||
final BounceAnimator prevBounceAnimator = new BounceAnimator(prevTextView, "translationX");
|
||||
prevBounceAnimator.queue(new Attributes(bounceDistance, BOUNCE1_MS));
|
||||
prevBounceAnimator.queue(new Attributes(-bounceDistance/4, BOUNCE2_MS));
|
||||
prevBounceAnimator.queue(new Attributes(0, BOUNCE4_MS));
|
||||
prevBounceAnimator.setStartDelay(ANIMATION_DELAY_MS);
|
||||
final BounceAnimatorBuilder prevBounceAnimatorBuilder = new BounceAnimatorBuilder(prevTextView, "translationX");
|
||||
prevBounceAnimatorBuilder.queue(new Attributes(bounceDistance, BOUNCE1_MS));
|
||||
prevBounceAnimatorBuilder.queue(new Attributes(-bounceDistance/4, BOUNCE2_MS));
|
||||
prevBounceAnimatorBuilder.queue(new Attributes(0, BOUNCE3_MS));
|
||||
|
||||
final BounceAnimator nextBounceAnimator = new BounceAnimator(nextTextView, "translationX");
|
||||
nextBounceAnimator.queue(new Attributes(-bounceDistance, BOUNCE1_MS));
|
||||
nextBounceAnimator.queue(new Attributes(bounceDistance/4, BOUNCE2_MS));
|
||||
nextBounceAnimator.queue(new Attributes(0, BOUNCE4_MS));
|
||||
nextBounceAnimator.setStartDelay(ANIMATION_DELAY_MS);
|
||||
final BounceAnimatorBuilder nextBounceAnimatorBuilder = new BounceAnimatorBuilder(nextTextView, "translationX");
|
||||
nextBounceAnimatorBuilder.queue(new Attributes(-bounceDistance, BOUNCE1_MS));
|
||||
nextBounceAnimatorBuilder.queue(new Attributes(bounceDistance/4, BOUNCE2_MS));
|
||||
nextBounceAnimatorBuilder.queue(new Attributes(0, BOUNCE3_MS));
|
||||
|
||||
TransitionsTracker.track(nextBounceAnimator);
|
||||
final AnimatorSet bounceAnimatorSet = new AnimatorSet();
|
||||
bounceAnimatorSet.playTogether(prevBounceAnimatorBuilder.build(), nextBounceAnimatorBuilder.build());
|
||||
|
||||
TransitionsTracker.track(nextBounceAnimatorBuilder);
|
||||
|
||||
final AnimatorSet titlesAnimatorSet = new AnimatorSet();
|
||||
titlesAnimatorSet.playTogether(alphaAnimatorSet, bounceAnimatorSet);
|
||||
titlesAnimatorSet.setStartDelay(ANIMATION_DELAY_MS);
|
||||
|
||||
// Start animations.
|
||||
alphaAnimatorSet.start();
|
||||
prevBounceAnimator.start();
|
||||
nextBounceAnimator.start();
|
||||
titlesAnimatorSet.start();
|
||||
}
|
||||
|
||||
private class PreDrawListener implements ViewTreeObserver.OnPreDrawListener {
|
||||
|
@ -144,7 +144,7 @@ gbjar.sources += [
|
||||
'AndroidGamepadManager.java',
|
||||
'animation/AnimationUtils.java',
|
||||
'animation/AnimatorProxy.java',
|
||||
'animation/BounceAnimator.java',
|
||||
'animation/BounceAnimatorBuilder.java',
|
||||
'animation/HeightChangeAnimation.java',
|
||||
'animation/PropertyAnimator.java',
|
||||
'animation/Rotate3DAnimation.java',
|
||||
|
@ -216,7 +216,7 @@ public class PromptInput {
|
||||
input.setCurrentMinute(calendar.get(GregorianCalendar.MINUTE));
|
||||
mView = (View)input;
|
||||
} else if (mType.equals("datetime-local") || mType.equals("datetime")) {
|
||||
DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd HH:mm", mValue,
|
||||
DateTimePicker input = new DateTimePicker(context, "yyyy-MM-dd HH:mm", mValue.replace("T"," ").replace("Z", ""),
|
||||
DateTimePicker.PickersState.DATETIME);
|
||||
input.toggleCalendar(true);
|
||||
mView = (View)input;
|
||||
@ -255,11 +255,11 @@ public class PromptInput {
|
||||
} else if (mType.equals("week")) {
|
||||
return formatDateString("yyyy-'W'ww",calendar);
|
||||
} else if (mType.equals("datetime-local")) {
|
||||
return formatDateString("yyyy-MM-dd HH:mm",calendar);
|
||||
return formatDateString("yyyy-MM-dd'T'HH:mm",calendar);
|
||||
} else if (mType.equals("datetime")) {
|
||||
calendar.set(GregorianCalendar.ZONE_OFFSET,0);
|
||||
calendar.setTimeInMillis(dp.getTimeInMillis());
|
||||
return formatDateString("yyyy-MM-dd HH:mm",calendar);
|
||||
return formatDateString("yyyy-MM-dd'T'HH:mm'Z'",calendar);
|
||||
} else if (mType.equals("month")) {
|
||||
return formatDateString("yyyy-MM",calendar);
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ var gMessageIds = [];
|
||||
* - text (string): Text to show as banner message
|
||||
* - url (string): URL to open when banner is clicked
|
||||
* - icon (data URI): Icon to appear in banner
|
||||
* - target_geo (string): Country code for where this message should be shown (e.g. "US")
|
||||
* - countries (list of strings): Country codes for where this message should be shown (e.g. ["US", "GR"])
|
||||
*/
|
||||
function updateBanner(messages) {
|
||||
// Remove the current messages, if there are any.
|
||||
@ -194,9 +194,10 @@ function updateBanner(messages) {
|
||||
|
||||
messages.forEach(function(message) {
|
||||
// Don't add this message to the banner if it's not supposed to be shown in this country.
|
||||
if ("target_geo" in message && message.target_geo != gCountryCode) {
|
||||
if ("countries" in message && message.countries.indexOf(gCountryCode) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
let id = Home.banner.add({
|
||||
text: message.text,
|
||||
icon: message.icon,
|
||||
|
@ -533,6 +533,9 @@ extract_signature(const char *src, uint32_t sigIndex, const char * dest)
|
||||
|
||||
/* Skip to the correct signature */
|
||||
for (i = 0; i <= sigIndex; i++) {
|
||||
/* Avoid leaking while skipping signatures */
|
||||
free(extractedSignature);
|
||||
|
||||
/* skip past the signature algorithm ID */
|
||||
if (fseeko(fpSrc, sizeof(uint32_t), SEEK_CUR)) {
|
||||
fprintf(stderr, "ERROR: Could not seek past sig algorithm ID.\n");
|
||||
|
@ -51,9 +51,10 @@ mar_read_entire_file(const char * filePath, uint32_t maxSize,
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "nsIFileStreams.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsIStringEnumerator.h"
|
||||
#include "nsIDirectoryEnumerator.h"
|
||||
#include "nsIZipReader.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsNSSCertificate.h"
|
||||
@ -98,7 +99,7 @@ ReadStream(const nsCOMPtr<nsIInputStream>& stream, /*out*/ SECItem& buf)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Finds exactly one (signature metadata) entry that matches the given
|
||||
// Finds exactly one (signature metadata) JAR entry that matches the given
|
||||
// search pattern, and then load it. Fails if there are no matches or if
|
||||
// there is more than one match. If bugDigest is not null then on success
|
||||
// bufDigest will contain the SHA-1 digeset of the entry.
|
||||
@ -153,27 +154,22 @@ FindAndLoadOneEntry(nsIZipReader * zip,
|
||||
// at once, which would require memory in proportion to the size of the largest
|
||||
// entry. Instead, we require only a small, fixed amount of memory.
|
||||
//
|
||||
// @param stream an input stream from a JAR entry or file depending on whether
|
||||
// it is from a signed archive or unpacked into a directory
|
||||
// @param digestFromManifest The digest that we're supposed to check the file's
|
||||
// contents against, from the manifest
|
||||
// @param buf A scratch buffer that we use for doing the I/O, which must have
|
||||
// already been allocated. The size of this buffer is the unit
|
||||
// size of our I/O.
|
||||
nsresult
|
||||
VerifyEntryContentDigest(nsIZipReader * zip, const nsACString & aFilename,
|
||||
const SECItem & digestFromManifest, SECItem & buf)
|
||||
VerifyStreamContentDigest(nsIInputStream* stream,
|
||||
const SECItem& digestFromManifest, SECItem& buf)
|
||||
{
|
||||
MOZ_ASSERT(buf.len > 0);
|
||||
if (digestFromManifest.len != SHA1_LENGTH)
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
|
||||
nsresult rv;
|
||||
|
||||
nsCOMPtr<nsIInputStream> stream;
|
||||
rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
|
||||
}
|
||||
|
||||
uint64_t len64;
|
||||
rv = stream->Available(&len64);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
@ -226,6 +222,81 @@ VerifyEntryContentDigest(nsIZipReader * zip, const nsACString & aFilename,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
VerifyEntryContentDigest(nsIZipReader* zip, const nsACString& aFilename,
|
||||
const SECItem& digestFromManifest, SECItem& buf)
|
||||
{
|
||||
nsCOMPtr<nsIInputStream> stream;
|
||||
nsresult rv = zip->GetInputStream(aFilename, getter_AddRefs(stream));
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
|
||||
}
|
||||
|
||||
return VerifyStreamContentDigest(stream, digestFromManifest, buf);
|
||||
}
|
||||
|
||||
// @oaram aDir directory containing the unpacked signed archive
|
||||
// @param aFilename path of the target file relative to aDir
|
||||
// @param digestFromManifest The digest that we're supposed to check the file's
|
||||
// contents against, from the manifest
|
||||
// @param buf A scratch buffer that we use for doing the I/O
|
||||
nsresult
|
||||
VerifyFileContentDigest(nsIFile* aDir, const nsAString& aFilename,
|
||||
const SECItem& digestFromManifest, SECItem& buf)
|
||||
{
|
||||
// Find the file corresponding to the manifest path
|
||||
nsCOMPtr<nsIFile> file;
|
||||
nsresult rv = aDir->Clone(getter_AddRefs(file));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// We don't know how to handle JARs with signed directory entries.
|
||||
// It's technically possible in the manifest but makes no sense on disk.
|
||||
// Inside an archive we just ignore them, but here we have to treat it
|
||||
// as an error because the signed bytes never got unpacked.
|
||||
int32_t pos = 0;
|
||||
int32_t slash;
|
||||
int32_t namelen = aFilename.Length();
|
||||
if (namelen == 0 || aFilename[namelen - 1] == '/') {
|
||||
return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
|
||||
}
|
||||
|
||||
// Append path segments one by one
|
||||
do {
|
||||
slash = aFilename.FindChar('/', pos);
|
||||
int32_t segend = (slash == kNotFound) ? namelen : slash;
|
||||
rv = file->Append(Substring(aFilename, pos, (segend - pos)));
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
|
||||
}
|
||||
pos = slash + 1;
|
||||
} while (pos < namelen && slash != kNotFound);
|
||||
|
||||
bool exists;
|
||||
rv = file->Exists(&exists);
|
||||
if (NS_FAILED(rv) || !exists) {
|
||||
return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
|
||||
}
|
||||
|
||||
bool isDir;
|
||||
rv = file->IsDirectory(&isDir);
|
||||
if (NS_FAILED(rv) || isDir) {
|
||||
// We only support signed files, not directory entries
|
||||
return NS_ERROR_SIGNED_JAR_ENTRY_INVALID;
|
||||
}
|
||||
|
||||
// Open an input stream for that file and verify it.
|
||||
nsCOMPtr<nsIInputStream> stream;
|
||||
rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file, -1, -1,
|
||||
nsIFileInputStream::CLOSE_ON_EOF);
|
||||
if (NS_FAILED(rv) || !stream) {
|
||||
return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
|
||||
}
|
||||
|
||||
return VerifyStreamContentDigest(stream, digestFromManifest, buf);
|
||||
}
|
||||
|
||||
// On input, nextLineStart is the start of the current line. On output,
|
||||
// nextLineStart is the start of the next line.
|
||||
nsresult
|
||||
@ -286,6 +357,7 @@ ReadLine(/*in/out*/ const char* & nextLineStart, /*out*/ nsCString & line,
|
||||
#define JAR_MF_SEARCH_STRING "(M|/M)ETA-INF/(M|m)(ANIFEST|anifest).(MF|mf)$"
|
||||
#define JAR_SF_SEARCH_STRING "(M|/M)ETA-INF/*.(SF|sf)$"
|
||||
#define JAR_RSA_SEARCH_STRING "(M|/M)ETA-INF/*.(RSA|rsa)$"
|
||||
#define JAR_META_DIR "META-INF"
|
||||
#define JAR_MF_HEADER "Manifest-Version: 1.0"
|
||||
#define JAR_SF_HEADER "Signature-Version: 1.0"
|
||||
|
||||
@ -949,3 +1021,497 @@ nsNSSCertificateDB::VerifySignedManifestAsync(
|
||||
aSignatureStream, aCallback));
|
||||
return task->Dispatch("SignedManifest");
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Signature verification for archives unpacked into a file structure
|
||||
//
|
||||
|
||||
// Finds the "*.rsa" signature file in the META-INF directory and returns
|
||||
// the name. It is an error if there are none or more than one .rsa file
|
||||
nsresult
|
||||
FindSignatureFilename(nsIFile* aMetaDir,
|
||||
/*out*/ nsAString& aFilename)
|
||||
{
|
||||
nsCOMPtr<nsISimpleEnumerator> entries;
|
||||
nsresult rv = aMetaDir->GetDirectoryEntries(getter_AddRefs(entries));
|
||||
nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(entries);
|
||||
if (NS_FAILED(rv) || !files) {
|
||||
return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
nsCOMPtr<nsIFile> file;
|
||||
rv = files->GetNextFile(getter_AddRefs(file));
|
||||
|
||||
while (NS_SUCCEEDED(rv) && file) {
|
||||
nsAutoString leafname;
|
||||
rv = file->GetLeafName(leafname);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
if (StringEndsWith(leafname, NS_LITERAL_STRING(".rsa"))) {
|
||||
if (!found) {
|
||||
found = true;
|
||||
aFilename = leafname;
|
||||
} else {
|
||||
// second signature file is an error
|
||||
rv = NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
rv = files->GetNextFile(getter_AddRefs(file));
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
rv = NS_ERROR_SIGNED_JAR_NOT_SIGNED;
|
||||
}
|
||||
|
||||
files->Close();
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Loads the signature metadata file that matches the given filename in
|
||||
// the passed-in Meta-inf directory. If bufDigest is not null then on
|
||||
// success bufDigest will contain the SHA-1 digest of the entry.
|
||||
nsresult
|
||||
LoadOneMetafile(nsIFile* aMetaDir,
|
||||
const nsAString& aFilename,
|
||||
/*out*/ SECItem& aBuf,
|
||||
/*optional, out*/ Digest* aBufDigest)
|
||||
{
|
||||
nsCOMPtr<nsIFile> metafile;
|
||||
nsresult rv = aMetaDir->Clone(getter_AddRefs(metafile));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = metafile->Append(aFilename);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
bool exists;
|
||||
rv = metafile->Exists(&exists);
|
||||
if (NS_FAILED(rv) || !exists) {
|
||||
// we can call a missing .rsa file "unsigned" but FindSignatureFilename()
|
||||
// already found one: missing other metadata files means a broken signature.
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIInputStream> stream;
|
||||
rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), metafile);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = ReadStream(stream, aBuf);
|
||||
stream->Close();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (aBufDigest) {
|
||||
rv = aBufDigest->DigestBuf(SEC_OID_SHA1, aBuf.data, aBuf.len - 1);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Parses MANIFEST.MF and verifies the contents of the unpacked files
|
||||
// listed in the manifest.
|
||||
// The filenames of all entries will be returned in aMfItems. aBuf must
|
||||
// be a pre-allocated scratch buffer that is used for doing I/O.
|
||||
nsresult
|
||||
ParseMFUnpacked(const char* aFilebuf, nsIFile* aDir,
|
||||
/*out*/ nsTHashtable<nsStringHashKey>& aMfItems,
|
||||
ScopedAutoSECItem& aBuf)
|
||||
{
|
||||
nsresult rv;
|
||||
|
||||
const char* nextLineStart = aFilebuf;
|
||||
|
||||
rv = CheckManifestVersion(nextLineStart, NS_LITERAL_CSTRING(JAR_MF_HEADER));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Skip the rest of the header section, which ends with a blank line.
|
||||
{
|
||||
nsAutoCString line;
|
||||
do {
|
||||
rv = ReadLine(nextLineStart, line);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
} while (line.Length() > 0);
|
||||
|
||||
// Manifest containing no file entries is OK, though useless.
|
||||
if (*nextLineStart == '\0') {
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
nsAutoString curItemName;
|
||||
ScopedAutoSECItem digest;
|
||||
|
||||
for (;;) {
|
||||
nsAutoCString curLine;
|
||||
rv = ReadLine(nextLineStart, curLine);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (curLine.Length() == 0) {
|
||||
// end of section (blank line or end-of-file)
|
||||
|
||||
if (curItemName.Length() == 0) {
|
||||
// '...Each section must start with an attribute with the name as
|
||||
// "Name",...', so every section must have a Name attribute.
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
if (digest.len == 0) {
|
||||
// We require every entry to have a digest, since we require every
|
||||
// entry to be signed and we don't allow duplicate entries.
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
if (aMfItems.Contains(curItemName)) {
|
||||
// Duplicate entry
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
// Verify that the file's content digest matches the digest from this
|
||||
// MF section.
|
||||
rv = VerifyFileContentDigest(aDir, curItemName, digest, aBuf);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
aMfItems.PutEntry(curItemName);
|
||||
|
||||
if (*nextLineStart == '\0') {
|
||||
// end-of-file
|
||||
break;
|
||||
}
|
||||
|
||||
// reset so we know we haven't encountered either of these for the next
|
||||
// item yet.
|
||||
curItemName.Truncate();
|
||||
digest.reset();
|
||||
|
||||
continue; // skip the rest of the loop below
|
||||
}
|
||||
|
||||
nsAutoCString attrName;
|
||||
nsAutoCString attrValue;
|
||||
rv = ParseAttribute(curLine, attrName, attrValue);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Lines to look for:
|
||||
|
||||
// (1) Digest:
|
||||
if (attrName.LowerCaseEqualsLiteral("sha1-digest")) {
|
||||
if (digest.len > 0) {
|
||||
// multiple SHA1 digests in section
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
rv = MapSECStatus(ATOB_ConvertAsciiToItem(&digest, attrValue.get()));
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// (2) Name: associates this manifest section with a file in the jar.
|
||||
if (attrName.LowerCaseEqualsLiteral("name")) {
|
||||
if (MOZ_UNLIKELY(curItemName.Length() > 0)) {
|
||||
// multiple names in section
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
if (MOZ_UNLIKELY(attrValue.Length() == 0)) {
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
curItemName = NS_ConvertUTF8toUTF16(attrValue);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// (3) Magic: the only other must-understand attribute
|
||||
if (attrName.LowerCaseEqualsLiteral("magic")) {
|
||||
// We don't understand any magic, so we can't verify an entry that
|
||||
// requires magic. Since we require every entry to have a valid
|
||||
// signature, we have no choice but to reject the entry.
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
// unrecognized attributes must be ignored
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// recursively check a directory tree for files not in the list of
|
||||
// verified files we found in the manifest. For each file we find
|
||||
// Check it against the files found in the manifest. If the file wasn't
|
||||
// in the manifest then it's unsigned and we can stop looking. Otherwise
|
||||
// remove it from the collection so we can check leftovers later.
|
||||
//
|
||||
// @param aDir Directory to check
|
||||
// @param aPath Relative path to that directory (to check against aItems)
|
||||
// @param aItems All the files found
|
||||
// @param *Filename signature files that won't be in the manifest
|
||||
nsresult
|
||||
CheckDirForUnsignedFiles(nsIFile* aDir,
|
||||
const nsString& aPath,
|
||||
/* in/out */ nsTHashtable<nsStringHashKey>& aItems,
|
||||
const nsAString& sigFilename,
|
||||
const nsAString& sfFilename,
|
||||
const nsAString& mfFilename)
|
||||
{
|
||||
nsCOMPtr<nsISimpleEnumerator> entries;
|
||||
nsresult rv = aDir->GetDirectoryEntries(getter_AddRefs(entries));
|
||||
nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(entries);
|
||||
if (NS_FAILED(rv) || !files) {
|
||||
return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
|
||||
}
|
||||
|
||||
bool inMeta = StringBeginsWith(aPath, NS_LITERAL_STRING(JAR_META_DIR));
|
||||
|
||||
while (NS_SUCCEEDED(rv)) {
|
||||
nsCOMPtr<nsIFile> file;
|
||||
rv = files->GetNextFile(getter_AddRefs(file));
|
||||
if (NS_FAILED(rv) || !file) {
|
||||
break;
|
||||
}
|
||||
|
||||
nsAutoString leafname;
|
||||
rv = file->GetLeafName(leafname);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoString curName(aPath + leafname);
|
||||
|
||||
bool isDir;
|
||||
rv = file->IsDirectory(&isDir);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// if it's a directory we need to recurse
|
||||
if (isDir) {
|
||||
curName.Append(NS_LITERAL_STRING("/"));
|
||||
rv = CheckDirForUnsignedFiles(file, curName, aItems,
|
||||
sigFilename, sfFilename, mfFilename);
|
||||
} else {
|
||||
// The files that comprise the signature mechanism are not covered by the
|
||||
// signature.
|
||||
//
|
||||
// XXX: This is OK for a single signature, but doesn't work for
|
||||
// multiple signatures because the metadata for the other signatures
|
||||
// is not signed either.
|
||||
if (inMeta && ( leafname == sigFilename ||
|
||||
leafname == sfFilename ||
|
||||
leafname == mfFilename )) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// make sure the current file was found in the manifest
|
||||
nsStringHashKey* item = aItems.GetEntry(curName);
|
||||
if (!item) {
|
||||
return NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY;
|
||||
}
|
||||
|
||||
// Remove the item so we can check for leftover items later
|
||||
aItems.RemoveEntry(curName);
|
||||
}
|
||||
}
|
||||
files->Close();
|
||||
return rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify the signature of a directory structure as if it were a
|
||||
* signed JAR file (used for unpacked JARs)
|
||||
*/
|
||||
nsresult
|
||||
VerifySignedDirectory(AppTrustedRoot aTrustedRoot,
|
||||
nsIFile* aDirectory,
|
||||
/*out, optional */ nsIX509Cert** aSignerCert)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aDirectory);
|
||||
|
||||
if (aSignerCert) {
|
||||
*aSignerCert = nullptr;
|
||||
}
|
||||
|
||||
// Make sure there's a META-INF directory
|
||||
|
||||
nsCOMPtr<nsIFile> metaDir;
|
||||
nsresult rv = aDirectory->Clone(getter_AddRefs(metaDir));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
rv = metaDir->Append(NS_LITERAL_STRING(JAR_META_DIR));
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool exists;
|
||||
rv = metaDir->Exists(&exists);
|
||||
if (NS_FAILED(rv) || !exists) {
|
||||
return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
|
||||
}
|
||||
bool isDirectory;
|
||||
rv = metaDir->IsDirectory(&isDirectory);
|
||||
if (NS_FAILED(rv) || !isDirectory) {
|
||||
return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
|
||||
}
|
||||
|
||||
// Find and load the Signature (RSA) file
|
||||
|
||||
nsAutoString sigFilename;
|
||||
rv = FindSignatureFilename(metaDir, sigFilename);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
ScopedAutoSECItem sigBuffer;
|
||||
rv = LoadOneMetafile(metaDir, sigFilename, sigBuffer, nullptr);
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_SIGNED_JAR_NOT_SIGNED;
|
||||
}
|
||||
|
||||
// Load the signature (SF) file and verify the signature.
|
||||
// The .sf and .rsa files must have the same name apart from the extension.
|
||||
|
||||
nsAutoString sfFilename(Substring(sigFilename, 0, sigFilename.Length() - 3)
|
||||
+ NS_LITERAL_STRING("sf"));
|
||||
|
||||
ScopedAutoSECItem sfBuffer;
|
||||
Digest sfCalculatedDigest;
|
||||
rv = LoadOneMetafile(metaDir, sfFilename, sfBuffer, &sfCalculatedDigest);
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
sigBuffer.type = siBuffer;
|
||||
ScopedCERTCertList builtChain;
|
||||
rv = VerifySignature(aTrustedRoot, sigBuffer, sfCalculatedDigest.get(),
|
||||
builtChain);
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
// Get the expected manifest hash from the signed .sf file
|
||||
|
||||
ScopedAutoSECItem mfDigest;
|
||||
rv = ParseSF(char_ptr_cast(sfBuffer.data), mfDigest);
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
// Load manifest (MF) file and verify signature
|
||||
|
||||
nsAutoString mfFilename(NS_LITERAL_STRING("manifest.mf"));
|
||||
ScopedAutoSECItem manifestBuffer;
|
||||
Digest mfCalculatedDigest;
|
||||
rv = LoadOneMetafile(metaDir, mfFilename, manifestBuffer, &mfCalculatedDigest);
|
||||
if (NS_FAILED(rv)) {
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
if (SECITEM_CompareItem(&mfDigest, &mfCalculatedDigest.get()) != SECEqual) {
|
||||
return NS_ERROR_SIGNED_JAR_MANIFEST_INVALID;
|
||||
}
|
||||
|
||||
// Parse manifest and verify signed hash of all listed files
|
||||
|
||||
// Allocate the I/O buffer only once per JAR, instead of once per entry, in
|
||||
// order to minimize malloc/free calls and in order to avoid fragmenting
|
||||
// memory.
|
||||
ScopedAutoSECItem buf(128 * 1024);
|
||||
|
||||
nsTHashtable<nsStringHashKey> items;
|
||||
rv = ParseMFUnpacked(char_ptr_cast(manifestBuffer.data),
|
||||
aDirectory, items, buf);
|
||||
if (NS_FAILED(rv)){
|
||||
return rv;
|
||||
}
|
||||
|
||||
// We've checked that everything listed in the manifest exists and is signed
|
||||
// correctly. Now check on disk for extra (unsigned) files.
|
||||
// Deletes found entries from items as it goes.
|
||||
rv = CheckDirForUnsignedFiles(aDirectory, EmptyString(), items,
|
||||
sigFilename, sfFilename, mfFilename);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// We verified that every entry that we require to be signed is signed. But,
|
||||
// were there any missing entries--that is, entries that are mentioned in the
|
||||
// manifest but missing from the directory tree? (There shouldn't be given
|
||||
// ParseMFUnpacked() checking them all, but it's a cheap sanity check.)
|
||||
if (items.Count() != 0) {
|
||||
return NS_ERROR_SIGNED_JAR_ENTRY_MISSING;
|
||||
}
|
||||
|
||||
// Return the signer's certificate to the reader if they want it.
|
||||
// XXX: We should return an nsIX509CertList with the whole validated chain.
|
||||
if (aSignerCert) {
|
||||
MOZ_ASSERT(CERT_LIST_HEAD(builtChain));
|
||||
nsCOMPtr<nsIX509Cert> signerCert =
|
||||
nsNSSCertificate::Create(CERT_LIST_HEAD(builtChain)->cert);
|
||||
NS_ENSURE_TRUE(signerCert, NS_ERROR_OUT_OF_MEMORY);
|
||||
signerCert.forget(aSignerCert);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
class VerifySignedDirectoryTask final : public CryptoTask
|
||||
{
|
||||
public:
|
||||
VerifySignedDirectoryTask(AppTrustedRoot aTrustedRoot, nsIFile* aUnpackedJar,
|
||||
nsIVerifySignedDirectoryCallback* aCallback)
|
||||
: mTrustedRoot(aTrustedRoot)
|
||||
, mDirectory(aUnpackedJar)
|
||||
, mCallback(new nsMainThreadPtrHolder<nsIVerifySignedDirectoryCallback>(aCallback))
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
virtual nsresult CalculateResult() override
|
||||
{
|
||||
return VerifySignedDirectory(mTrustedRoot,
|
||||
mDirectory,
|
||||
getter_AddRefs(mSignerCert));
|
||||
}
|
||||
|
||||
// This class doesn't directly hold NSS resources so there's nothing that
|
||||
// needs to be released
|
||||
virtual void ReleaseNSSResources() override { }
|
||||
|
||||
virtual void CallCallback(nsresult rv) override
|
||||
{
|
||||
(void) mCallback->VerifySignedDirectoryFinished(rv, mSignerCert);
|
||||
}
|
||||
|
||||
const AppTrustedRoot mTrustedRoot;
|
||||
const nsCOMPtr<nsIFile> mDirectory;
|
||||
nsMainThreadPtrHandle<nsIVerifySignedDirectoryCallback> mCallback;
|
||||
nsCOMPtr<nsIX509Cert> mSignerCert; // out
|
||||
};
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNSSCertificateDB::VerifySignedDirectoryAsync(
|
||||
AppTrustedRoot aTrustedRoot, nsIFile* aUnpackedJar,
|
||||
nsIVerifySignedDirectoryCallback* aCallback)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aUnpackedJar);
|
||||
NS_ENSURE_ARG_POINTER(aCallback);
|
||||
RefPtr<VerifySignedDirectoryTask> task(new VerifySignedDirectoryTask(aTrustedRoot,
|
||||
aUnpackedJar,
|
||||
aCallback));
|
||||
return task->Dispatch("UnpackedJar");
|
||||
}
|
||||
|
@ -28,6 +28,13 @@ interface nsIOpenSignedAppFileCallback : nsISupports
|
||||
in nsIX509Cert aSignerCert);
|
||||
};
|
||||
|
||||
[scriptable, function, uuid(d5f97827-622a-488f-be08-d850432ac8ec)]
|
||||
interface nsIVerifySignedDirectoryCallback : nsISupports
|
||||
{
|
||||
void verifySignedDirectoryFinished(in nsresult rv,
|
||||
in nsIX509Cert aSignerCert);
|
||||
};
|
||||
|
||||
[scriptable, function, uuid(3d6a9c87-5c5f-46fc-9410-96da6092f0f2)]
|
||||
interface nsIVerifySignedManifestCallback : nsISupports
|
||||
{
|
||||
@ -39,7 +46,7 @@ interface nsIVerifySignedManifestCallback : nsISupports
|
||||
* This represents a service to access and manipulate
|
||||
* X.509 certificates stored in a database.
|
||||
*/
|
||||
[scriptable, uuid(560bc9ac-3e71-472e-9b08-2270d0c71878)]
|
||||
[scriptable, uuid(6d27211b-7119-4777-9c62-f29310eeb262)]
|
||||
interface nsIX509CertDB : nsISupports {
|
||||
|
||||
/**
|
||||
@ -317,6 +324,22 @@ interface nsIX509CertDB : nsISupports {
|
||||
in nsIFile aJarFile,
|
||||
in nsIOpenSignedAppFileCallback callback);
|
||||
|
||||
/**
|
||||
* Verifies the signature on a directory representing an unpacked signed
|
||||
* JAR file. To be considered valid, there must be exactly one signature
|
||||
* on the directory structure and that signature must have signed every
|
||||
* entry. Further, the signature must come from a certificate that
|
||||
* is trusted for code signing.
|
||||
*
|
||||
* On success NS_OK and the trusted certificate that signed the
|
||||
* unpacked JAR are returned.
|
||||
*
|
||||
* On failure, an error code is returned.
|
||||
*/
|
||||
void verifySignedDirectoryAsync(in AppTrustedRoot trustedRoot,
|
||||
in nsIFile aUnpackedDir,
|
||||
in nsIVerifySignedDirectoryCallback callback);
|
||||
|
||||
/**
|
||||
* Given streams containing a signature and a manifest file, verifies
|
||||
* that the signature is valid for the manifest. The signature must
|
||||
|
BIN
security/manager/ssl/tests/unit/test_signed_apps/sslcontrol.xpi
Normal file
BIN
security/manager/ssl/tests/unit/test_signed_apps/sslcontrol.xpi
Normal file
Binary file not shown.
173
security/manager/ssl/tests/unit/test_signed_dir.js
Normal file
173
security/manager/ssl/tests/unit/test_signed_dir.js
Normal file
@ -0,0 +1,173 @@
|
||||
"use strict";
|
||||
|
||||
Components.utils.import("resource://gre/modules/ZipUtils.jsm");
|
||||
|
||||
do_get_profile(); // must be called before getting nsIX509CertDB
|
||||
const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(Ci.nsIX509CertDB);
|
||||
|
||||
var gSignedXPI = do_get_file("test_signed_apps/sslcontrol.xpi", false);
|
||||
var gTarget = FileUtils.getDir("TmpD", ["test_signed_dir"]);
|
||||
gTarget.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
||||
|
||||
|
||||
// tamper data structure, each element is optional
|
||||
// { copy: [[path,newname], [path2,newname2], ...],
|
||||
// delete: [path, path2, ...],
|
||||
// corrupt: [path, path2, ...]
|
||||
// }
|
||||
|
||||
function prepare(tamper) {
|
||||
ZipUtils.extractFiles(gSignedXPI, gTarget);
|
||||
|
||||
// copy files
|
||||
if (tamper.copy) {
|
||||
tamper.copy.forEach(i => {
|
||||
let f = gTarget.clone();
|
||||
i[0].split('/').forEach(seg => {f.append(seg);});
|
||||
f.copyTo(null, i[1]);
|
||||
});
|
||||
}
|
||||
|
||||
// delete files
|
||||
if (tamper.delete) {
|
||||
tamper.delete.forEach(i => {
|
||||
let f = gTarget.clone();
|
||||
i.split('/').forEach(seg => {f.append(seg);});
|
||||
f.remove(true);
|
||||
});
|
||||
}
|
||||
|
||||
// corrupt files
|
||||
if (tamper.corrupt) {
|
||||
tamper.corrupt.forEach(i => {
|
||||
let f = gTarget.clone();
|
||||
i.split('/').forEach(seg => {f.append(seg);});
|
||||
let s = FileUtils.openFileOutputStream(f, FileUtils.MODE_WRONLY);
|
||||
const str = "Kilroy was here";
|
||||
s.write(str, str.length);
|
||||
s.close();
|
||||
});
|
||||
}
|
||||
|
||||
return gTarget;
|
||||
}
|
||||
|
||||
|
||||
function check_result(name, expectedRv, dir) {
|
||||
return function verifySignedDirCallback(rv, aSignerCert) {
|
||||
equal(rv, expectedRv, name + " rv:");
|
||||
equal(aSignerCert != null, Components.isSuccessCode(expectedRv),
|
||||
"expecting certificate:");
|
||||
// cleanup and kick off next test
|
||||
dir.remove(true);
|
||||
run_next_test();
|
||||
};
|
||||
}
|
||||
|
||||
function verifyDirAsync(name, expectedRv, tamper) {
|
||||
let targetDir = prepare(tamper);
|
||||
certdb.verifySignedDirectoryAsync(
|
||||
Ci.nsIX509CertDB.AddonsPublicRoot, targetDir,
|
||||
check_result(name, expectedRv, targetDir));
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// the tests
|
||||
//
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'valid'", Cr.NS_OK, {} /* no tampering */ );
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'no meta dir'", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED,
|
||||
{delete: ["META-INF"]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'empty meta dir'", Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED,
|
||||
{delete: ["META-INF/mozilla.rsa",
|
||||
"META-INF/mozilla.sf",
|
||||
"META-INF/manifest.mf"]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'two rsa files'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
|
||||
{copy: [["META-INF/mozilla.rsa","extra.rsa"]]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'corrupt rsa file'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
|
||||
{corrupt: ["META-INF/mozilla.rsa"]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'missing sf file'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
|
||||
{delete: ["META-INF/mozilla.sf"]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'corrupt sf file'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
|
||||
{corrupt: ["META-INF/mozilla.sf"]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'extra .sf file (invalid)'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY,
|
||||
{copy: [["META-INF/mozilla.rsa","extra.sf"]]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'extra .sf file (valid)'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY,
|
||||
{copy: [["META-INF/mozilla.sf","extra.sf"]]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'missing manifest'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
|
||||
{delete: ["META-INF/manifest.mf"]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'corrupt manifest'", Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID,
|
||||
{corrupt: ["META-INF/manifest.mf"]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'missing file'", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING,
|
||||
{delete: ["bootstrap.js"]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'corrupt file'", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY,
|
||||
{corrupt: ["bootstrap.js"]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'extra file'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY,
|
||||
{copy: [["bootstrap.js","extra"]]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'missing file in dir'", Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING,
|
||||
{delete: ["content/options.xul"]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'corrupt file in dir'", Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY,
|
||||
{corrupt: ["content/options.xul"]});
|
||||
});
|
||||
|
||||
add_test(function() {
|
||||
verifyDirAsync("'extra file in dir'", Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY,
|
||||
{copy: [["content/options.xul","extra"]]});
|
||||
});
|
||||
|
||||
do_register_cleanup(function() {
|
||||
if (gTarget.exists()) {
|
||||
gTarget.remove(true);
|
||||
}
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
@ -70,6 +70,7 @@ run-sequentially = hardcoded ports
|
||||
[test_cert_version.js]
|
||||
[test_signed_apps.js]
|
||||
[test_signed_apps-marketplace.js]
|
||||
[test_signed_dir.js]
|
||||
|
||||
[test_cert_eku-CA_EP.js]
|
||||
[test_cert_eku-CA_EP_NS_OS_SA_TS.js]
|
||||
|
@ -19,7 +19,26 @@ function promiseNotification(topic) {
|
||||
});
|
||||
}
|
||||
|
||||
// Just enough mocks so we can avoid hawk etc.
|
||||
// Just enough mocks so we can avoid hawk and storage.
|
||||
let MockStorage = function() {
|
||||
this.data = null;
|
||||
};
|
||||
MockStorage.prototype = Object.freeze({
|
||||
set: function (contents) {
|
||||
this.data = contents;
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
get: function () {
|
||||
return Promise.resolve(this.data);
|
||||
},
|
||||
getOAuthTokens() {
|
||||
return Promise.resolve(null);
|
||||
},
|
||||
setOAuthTokens(contents) {
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
function MockFxAccountsClient() {
|
||||
this._email = "nobody@example.com";
|
||||
this._verified = false;
|
||||
@ -43,6 +62,7 @@ function MockFxAccounts(mockGrantClient) {
|
||||
return new FxAccounts({
|
||||
fxAccountsClient: new MockFxAccountsClient(),
|
||||
getAssertion: () => Promise.resolve("assertion"),
|
||||
signedInUserStorage: new MockStorage(),
|
||||
_destroyOAuthToken: function(tokenData) {
|
||||
// somewhat sad duplication of _destroyOAuthToken, but hard to avoid.
|
||||
return mockGrantClient.destroyToken(tokenData.token).then( () => {
|
||||
|
@ -248,9 +248,40 @@
|
||||
var testWin = windowMediator.getMostRecentWindow(winType);
|
||||
|
||||
setStatus("Running...");
|
||||
testWin.focus();
|
||||
var Tester = new testWin.Tester(links, gDumper, testsFinished);
|
||||
Tester.start();
|
||||
|
||||
// It's possible that the test harness window is not yet focused when this
|
||||
// function runs (in which case testWin is already focused, and focusing it
|
||||
// will be a no-op, and then the test harness window will steal focus later,
|
||||
// which will mess up tests). So wait for the test harness window to be
|
||||
// focused before trying to focus testWin.
|
||||
waitForFocus(() => {
|
||||
// Focus the test window and start tests.
|
||||
waitForFocus(() => {
|
||||
var Tester = new testWin.Tester(links, gDumper, testsFinished);
|
||||
Tester.start();
|
||||
}, testWin);
|
||||
}, window);
|
||||
}
|
||||
|
||||
function executeSoon(callback) {
|
||||
let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
|
||||
tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
function waitForFocus(callback, win) {
|
||||
// If "win" is already focused, just call the callback.
|
||||
let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
|
||||
if (fm.focusedWindow == win) {
|
||||
executeSoon(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise focus it, and wait for the focus event.
|
||||
win.addEventListener("focus", function listener() {
|
||||
win.removeEventListener("focus", listener, true);
|
||||
executeSoon(callback);
|
||||
}, true);
|
||||
win.focus();
|
||||
}
|
||||
|
||||
function sum(a, b) {
|
||||
|
244
toolkit/components/passwordmgr/LoginDoorhangers.jsm
Normal file
244
toolkit/components/passwordmgr/LoginDoorhangers.jsm
Normal file
@ -0,0 +1,244 @@
|
||||
/* 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";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"LoginDoorhangers",
|
||||
];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
this.LoginDoorhangers = {};
|
||||
|
||||
/**
|
||||
* Doorhanger for selecting and filling logins.
|
||||
*
|
||||
* @param {Object} properties
|
||||
* Properties from this object will be applied to the new instance.
|
||||
*/
|
||||
this.LoginDoorhangers.FillDoorhanger = function (properties) {
|
||||
this.onFilterInput = this.onFilterInput.bind(this);
|
||||
this.onListDblClick = this.onListDblClick.bind(this);
|
||||
this.onListKeyPress = this.onListKeyPress.bind(this);
|
||||
|
||||
this.filterString = properties.filterString;
|
||||
|
||||
if (properties.browser) {
|
||||
this.browser = properties.browser;
|
||||
}
|
||||
};
|
||||
|
||||
this.LoginDoorhangers.FillDoorhanger.prototype = {
|
||||
/**
|
||||
* Whether the elements for this doorhanger are currently in the document.
|
||||
*/
|
||||
bound: false,
|
||||
|
||||
/**
|
||||
* Associates the doorhanger with its browser. When the tab associated to this
|
||||
* browser is selected, the anchor icon for the doorhanger will appear.
|
||||
*
|
||||
* The browser may change during the lifetime of the doorhanger, in case the
|
||||
* web page is moved to a different chrome window by the swapDocShells method.
|
||||
*/
|
||||
set browser(browser) {
|
||||
this._browser = browser;
|
||||
|
||||
let doorhanger = this;
|
||||
let PopupNotifications = this.chomeDocument.defaultView.PopupNotifications;
|
||||
let notification = PopupNotifications.show(
|
||||
browser,
|
||||
"password-fill",
|
||||
"",
|
||||
"password-notification-icon",
|
||||
null,
|
||||
null,
|
||||
{
|
||||
dismissed: true,
|
||||
persistWhileVisible: true,
|
||||
eventCallback: function (topic, otherBrowser) {
|
||||
switch (topic) {
|
||||
case "shown":
|
||||
// Since we specified the "dismissed" option, this event will only
|
||||
// be called after the "show" method returns, so the reference to
|
||||
// "this.notification" will be available at this point.
|
||||
doorhanger.bound = true;
|
||||
doorhanger.bind();
|
||||
break;
|
||||
|
||||
case "dismissed":
|
||||
case "removed":
|
||||
if (doorhanger.bound) {
|
||||
doorhanger.unbind();
|
||||
}
|
||||
break;
|
||||
|
||||
case "swapping":
|
||||
this._browser = otherBrowser;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this.notification = notification;
|
||||
notification.doorhanger = this;
|
||||
},
|
||||
get browser() {
|
||||
return this._browser;
|
||||
},
|
||||
_browser: null,
|
||||
|
||||
/**
|
||||
* DOM document to which the doorhanger is currently associated.
|
||||
*
|
||||
* This may change during the lifetime of the doorhanger, in case the web page
|
||||
* is moved to a different chrome window by the swapDocShells method.
|
||||
*/
|
||||
get chomeDocument() {
|
||||
return this.browser.ownerDocument;
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides this notification, if the notification panel is currently open.
|
||||
*/
|
||||
hide() {
|
||||
let PopupNotifications = this.chomeDocument.defaultView.PopupNotifications;
|
||||
if (PopupNotifications.isPanelOpen) {
|
||||
PopupNotifications.panel.hidePopup();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the doorhanger from the browser.
|
||||
*/
|
||||
remove() {
|
||||
this.notification.remove();
|
||||
},
|
||||
|
||||
/**
|
||||
* Binds this doorhanger to its UI controls.
|
||||
*/
|
||||
bind() {
|
||||
this.element = this.chomeDocument.getElementById("login-fill-doorhanger");
|
||||
this.list = this.chomeDocument.getElementById("login-fill-list");
|
||||
this.filter = this.chomeDocument.getElementById("login-fill-filter");
|
||||
|
||||
this.filter.setAttribute("value", this.filterString);
|
||||
|
||||
this.refreshList();
|
||||
|
||||
this.filter.addEventListener("input", this.onFilterInput);
|
||||
this.list.addEventListener("dblclick", this.onListDblClick);
|
||||
this.list.addEventListener("keypress", this.onListKeyPress);
|
||||
|
||||
// Move the main element to the notification panel for displaying.
|
||||
this.notification.owner.panel.firstElementChild.appendChild(this.element);
|
||||
this.element.hidden = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unbinds this doorhanger from its UI controls.
|
||||
*/
|
||||
unbind() {
|
||||
this.filter.removeEventListener("input", this.onFilterInput);
|
||||
this.list.removeEventListener("dblclick", this.onListDblClick);
|
||||
this.list.removeEventListener("keypress", this.onListKeyPress);
|
||||
|
||||
this.clearList();
|
||||
|
||||
// Place the element back in the document for the next time we need it.
|
||||
this.element.hidden = true;
|
||||
this.chomeDocument.getElementById("mainPopupSet").appendChild(this.element);
|
||||
},
|
||||
|
||||
/**
|
||||
* User-editable string used to filter the list of all logins.
|
||||
*/
|
||||
filterString: "",
|
||||
|
||||
/**
|
||||
* Handles text changes in the filter textbox.
|
||||
*/
|
||||
onFilterInput() {
|
||||
this.filterString = this.filter.value;
|
||||
this.refreshList();
|
||||
},
|
||||
|
||||
/**
|
||||
* Rebuilds the list of logins.
|
||||
*/
|
||||
refreshList() {
|
||||
this.clearList();
|
||||
|
||||
let formLogins = Services.logins.findLogins({}, "", "", null);
|
||||
let filterToUse = this.filterString.trim().toLowerCase();
|
||||
if (filterToUse) {
|
||||
formLogins = formLogins.filter(login => {
|
||||
return login.hostname.toLowerCase().indexOf(filterToUse) != -1 ||
|
||||
login.username.toLowerCase().indexOf(filterToUse) != -1 ;
|
||||
});
|
||||
}
|
||||
|
||||
for (let { hostname, username } of formLogins) {
|
||||
let item = this.chomeDocument.createElementNS(XUL_NS, "richlistitem");
|
||||
item.classList.add("login-fill-item");
|
||||
item.setAttribute("hostname", hostname);
|
||||
item.setAttribute("username", username);
|
||||
this.list.appendChild(item);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the list of logins.
|
||||
*/
|
||||
clearList() {
|
||||
while (this.list.firstChild) {
|
||||
this.list.removeChild(this.list.firstChild);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the action associated to a login item.
|
||||
*/
|
||||
onListDblClick(event) {
|
||||
if (event.button != 0 || !this.list.selectedItem) {
|
||||
return;
|
||||
}
|
||||
this.fillLogin();
|
||||
},
|
||||
onListKeyPress(event) {
|
||||
if (event.keyCode != Ci.nsIDOMKeyEvent.DOM_VK_RETURN ||
|
||||
!this.list.selectedItem) {
|
||||
return;
|
||||
}
|
||||
this.fillLogin();
|
||||
},
|
||||
fillLogin() {
|
||||
this.hide();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves an existing FillDoorhanger associated with a browser, or null if an
|
||||
* associated doorhanger of that type cannot be found.
|
||||
*
|
||||
* @param An object with the following properties:
|
||||
* {
|
||||
* browser:
|
||||
* The <browser> element to which the doorhanger is associated.
|
||||
* }
|
||||
*/
|
||||
this.LoginDoorhangers.FillDoorhanger.find = function ({ browser }) {
|
||||
let PopupNotifications = browser.ownerDocument.defaultView.PopupNotifications;
|
||||
let notification = PopupNotifications.getNotification("password-fill",
|
||||
browser);
|
||||
return notification && notification.doorhanger;
|
||||
};
|
22
toolkit/components/passwordmgr/content/login.xml
Normal file
22
toolkit/components/passwordmgr/content/login.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
|
||||
<!DOCTYPE bindings SYSTEM "chrome://passwordmgr/locale/passwordManager.dtd">
|
||||
|
||||
<bindings id="login-bindings"
|
||||
xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl">
|
||||
|
||||
<binding id="login"
|
||||
extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
|
||||
<content orient="vertical">
|
||||
<xul:label class="login-hostname" crop="end"
|
||||
xbl:inherits="value=hostname"/>
|
||||
<xul:label class="login-username" crop="end"
|
||||
xbl:inherits="value=username"/>
|
||||
</content>
|
||||
</binding>
|
||||
</bindings>
|
@ -4,6 +4,7 @@
|
||||
|
||||
toolkit.jar:
|
||||
% content passwordmgr %content/passwordmgr/
|
||||
content/passwordmgr/login.xml (content/login.xml)
|
||||
* content/passwordmgr/passwordManager.xul (content/passwordManager.xul)
|
||||
* content/passwordmgr/passwordManager.js (content/passwordManager.js)
|
||||
* content/passwordmgr/passwordManagerExceptions.js (content/passwordManagerExceptions.js)
|
||||
|
@ -41,6 +41,7 @@ EXTRA_PP_JS_MODULES += [
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'InsecurePasswordUtils.jsm',
|
||||
'LoginDoorhangers.jsm',
|
||||
'LoginHelper.jsm',
|
||||
'LoginManagerContent.jsm',
|
||||
'LoginRecipes.jsm',
|
||||
|
@ -1415,7 +1415,7 @@ interface nsINavHistoryService : nsISupports
|
||||
/**
|
||||
* @see runInBatchMode of nsINavHistoryService/nsINavBookmarksService
|
||||
*/
|
||||
[scriptable, uuid(5143f2bb-be0a-4faf-9acb-b0ed3f82952c)]
|
||||
[scriptable, function, uuid(5a5a9154-95ac-4e3d-90df-558816297407)]
|
||||
interface nsINavHistoryBatchCallback : nsISupports {
|
||||
void runBatched(in nsISupports aUserData);
|
||||
};
|
||||
|
@ -4186,10 +4186,13 @@ nsNavHistory::URIToResultNode(nsIURI* aURI,
|
||||
true, tagsFragment);
|
||||
// Should match kGetInfoIndex_*
|
||||
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
|
||||
"SELECT h.id, :page_url, h.title, h.rev_host, h.visit_count, "
|
||||
"h.last_visit_date, f.url, null, null, null, null, "
|
||||
) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid "
|
||||
"SELECT h.id, :page_url, COALESCE(b.title, h.title), "
|
||||
"h.rev_host, h.visit_count, h.last_visit_date, f.url, "
|
||||
"b.id, b.dateAdded, b.lastModified, b.parent, "
|
||||
) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
|
||||
"b.guid, b.position, b.type, b.fk "
|
||||
"FROM moz_places h "
|
||||
"LEFT JOIN moz_bookmarks b ON b.fk = h.id "
|
||||
"LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
|
||||
"WHERE h.url = :page_url ")
|
||||
);
|
||||
|
@ -2767,7 +2767,7 @@ nsNavHistoryQueryResultNode::NotifyIfTagsChanged(nsIURI* aURI)
|
||||
nsCOMArray<nsNavHistoryResultNode> matches;
|
||||
RecursiveFindURIs(onlyOneEntry, this, spec, &matches);
|
||||
|
||||
if (matches.Count() == 0 && mHasSearchTerms && !mRemovingURI) {
|
||||
if (matches.Count() == 0 && mHasSearchTerms) {
|
||||
// A new tag has been added, it's possible it matches our query.
|
||||
NS_ENSURE_TRUE(history, NS_ERROR_OUT_OF_MEMORY);
|
||||
rv = history->URIToResultNode(aURI, mOptions, getter_AddRefs(node));
|
||||
@ -2838,7 +2838,6 @@ nsNavHistoryQueryResultNode::OnItemRemoved(int64_t aItemId,
|
||||
const nsACString& aGUID,
|
||||
const nsACString& aParentGUID)
|
||||
{
|
||||
mRemovingURI = aURI;
|
||||
if (aItemType == nsINavBookmarksService::TYPE_BOOKMARK &&
|
||||
mLiveUpdate != QUERYUPDATE_SIMPLE && mLiveUpdate != QUERYUPDATE_TIME) {
|
||||
nsresult rv = Refresh();
|
||||
|
@ -674,7 +674,6 @@ public:
|
||||
virtual void RecursiveSort(const char* aData,
|
||||
SortComparator aComparator) override;
|
||||
|
||||
nsCOMPtr<nsIURI> mRemovingURI;
|
||||
nsresult NotifyIfTagsChanged(nsIURI* aURI);
|
||||
|
||||
uint32_t mBatchChanges;
|
||||
|
@ -107,30 +107,31 @@ TaggingService.prototype = {
|
||||
* @throws Cr.NS_ERROR_INVALID_ARG if any element of the input array is not
|
||||
* a valid tag.
|
||||
*/
|
||||
_convertInputMixedTagsArray: function TS__convertInputMixedTagsArray(aTags, trim=false)
|
||||
{
|
||||
return aTags.map(function (val)
|
||||
{
|
||||
let tag = { _self: this };
|
||||
if (typeof(val) == "number" && this._tagFolders[val]) {
|
||||
_convertInputMixedTagsArray(aTags, trim=false) {
|
||||
// Handle sparse array with a .filter.
|
||||
return aTags.filter(tag => tag !== undefined)
|
||||
.map(idOrName => {
|
||||
let tag = {};
|
||||
if (typeof(idOrName) == "number" && this._tagFolders[idOrName]) {
|
||||
// This is a tag folder id.
|
||||
tag.id = val;
|
||||
tag.id = idOrName;
|
||||
// We can't know the name at this point, since a previous tag could
|
||||
// want to change it.
|
||||
tag.__defineGetter__("name", function () this._self._tagFolders[this.id]);
|
||||
tag.__defineGetter__("name", () => this._tagFolders[tag.id]);
|
||||
}
|
||||
else if (typeof(val) == "string" && val.length > 0 && val.length <= Ci.nsITaggingService.MAX_TAG_LENGTH) {
|
||||
else if (typeof(idOrName) == "string" && idOrName.length > 0 &&
|
||||
idOrName.length <= Ci.nsITaggingService.MAX_TAG_LENGTH) {
|
||||
// This is a tag name.
|
||||
tag.name = trim ? val.trim() : val;
|
||||
tag.name = trim ? idOrName.trim() : idOrName;
|
||||
// We can't know the id at this point, since a previous tag could
|
||||
// have created it.
|
||||
tag.__defineGetter__("id", function () this._self._getItemIdForTag(this.name));
|
||||
tag.__defineGetter__("id", () => this._getItemIdForTag(tag.name));
|
||||
}
|
||||
else {
|
||||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
return tag;
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
// nsITaggingService
|
||||
@ -143,35 +144,37 @@ TaggingService.prototype = {
|
||||
// This also does some input validation.
|
||||
let tags = this._convertInputMixedTagsArray(aTags, true);
|
||||
|
||||
let taggingService = this;
|
||||
PlacesUtils.bookmarks.runInBatchMode({
|
||||
runBatched: function (aUserData)
|
||||
{
|
||||
tags.forEach(function (tag)
|
||||
{
|
||||
if (tag.id == -1) {
|
||||
// Tag does not exist yet, create it.
|
||||
this._createTag(tag.name);
|
||||
}
|
||||
let taggingFunction = () => {
|
||||
for (let tag of tags) {
|
||||
if (tag.id == -1) {
|
||||
// Tag does not exist yet, create it.
|
||||
this._createTag(tag.name);
|
||||
}
|
||||
|
||||
if (this._getItemIdForTaggedURI(aURI, tag.name) == -1) {
|
||||
// The provided URI is not yet tagged, add a tag for it.
|
||||
// Note that bookmarks under tag containers must have null titles.
|
||||
PlacesUtils.bookmarks.insertBookmark(
|
||||
tag.id, aURI, PlacesUtils.bookmarks.DEFAULT_INDEX, null
|
||||
);
|
||||
}
|
||||
if (this._getItemIdForTaggedURI(aURI, tag.name) == -1) {
|
||||
// The provided URI is not yet tagged, add a tag for it.
|
||||
// Note that bookmarks under tag containers must have null titles.
|
||||
PlacesUtils.bookmarks.insertBookmark(
|
||||
tag.id, aURI, PlacesUtils.bookmarks.DEFAULT_INDEX, null
|
||||
);
|
||||
}
|
||||
|
||||
// Try to preserve user's tag name casing.
|
||||
// Rename the tag container so the Places view matches the most-recent
|
||||
// user-typed value.
|
||||
if (PlacesUtils.bookmarks.getItemTitle(tag.id) != tag.name) {
|
||||
// this._tagFolders is updated by the bookmarks observer.
|
||||
PlacesUtils.bookmarks.setItemTitle(tag.id, tag.name);
|
||||
}
|
||||
}, taggingService);
|
||||
// Try to preserve user's tag name casing.
|
||||
// Rename the tag container so the Places view matches the most-recent
|
||||
// user-typed value.
|
||||
if (PlacesUtils.bookmarks.getItemTitle(tag.id) != tag.name) {
|
||||
// this._tagFolders is updated by the bookmarks observer.
|
||||
PlacesUtils.bookmarks.setItemTitle(tag.id, tag.name);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
};
|
||||
|
||||
// Use a batch only if creating more than 2 tags.
|
||||
if (tags.length < 3) {
|
||||
taggingFunction();
|
||||
} else {
|
||||
PlacesUtils.bookmarks.runInBatchMode(taggingFunction, null);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -225,23 +228,25 @@ TaggingService.prototype = {
|
||||
"https://bugzilla.mozilla.org/show_bug.cgi?id=967196");
|
||||
}
|
||||
|
||||
let taggingService = this;
|
||||
PlacesUtils.bookmarks.runInBatchMode({
|
||||
runBatched: function (aUserData)
|
||||
{
|
||||
tags.forEach(function (tag)
|
||||
{
|
||||
if (tag.id != -1) {
|
||||
// A tag could exist.
|
||||
let itemId = this._getItemIdForTaggedURI(aURI, tag.name);
|
||||
if (itemId != -1) {
|
||||
// There is a tagged item.
|
||||
PlacesUtils.bookmarks.removeItem(itemId);
|
||||
}
|
||||
let untaggingFunction = () => {
|
||||
for (let tag of tags) {
|
||||
if (tag.id != -1) {
|
||||
// A tag could exist.
|
||||
let itemId = this._getItemIdForTaggedURI(aURI, tag.name);
|
||||
if (itemId != -1) {
|
||||
// There is a tagged item.
|
||||
PlacesUtils.bookmarks.removeItem(itemId);
|
||||
}
|
||||
}, taggingService);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
};
|
||||
|
||||
// Use a batch only if creating more than 2 tags.
|
||||
if (tags.length < 3) {
|
||||
untaggingFunction();
|
||||
} else {
|
||||
PlacesUtils.bookmarks.runInBatchMode(untaggingFunction, null);
|
||||
}
|
||||
},
|
||||
|
||||
// nsITaggingService
|
||||
|
@ -143,8 +143,6 @@ add_test(function onItemChanged_tags_bookmark() {
|
||||
const TITLE = "New title";
|
||||
const TAG = "tag"
|
||||
gBookmarksObserver.expected = [
|
||||
{ name: "onBeginUpdateBatch", // Tag addition uses a batch.
|
||||
args: [] },
|
||||
{ name: "onItemAdded", // This is the tag folder.
|
||||
args: [
|
||||
{ name: "itemId", check: function (v) typeof(v) == "number" && v > 0 },
|
||||
@ -181,10 +179,6 @@ add_test(function onItemChanged_tags_bookmark() {
|
||||
{ name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
|
||||
{ name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
|
||||
] },
|
||||
{ name: "onEndUpdateBatch",
|
||||
args: [] },
|
||||
{ name: "onBeginUpdateBatch", // Tag removal uses a batch.
|
||||
args: [] },
|
||||
{ name: "onItemRemoved", // This is the tag.
|
||||
args: [
|
||||
{ name: "itemId", check: function (v) typeof(v) == "number" && v > 0 },
|
||||
@ -217,8 +211,6 @@ add_test(function onItemChanged_tags_bookmark() {
|
||||
{ name: "guid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
|
||||
{ name: "parentGuid", check: function (v) typeof(v) == "string" && /^[a-zA-Z0-9\-_]{12}$/.test(v) },
|
||||
] },
|
||||
{ name: "onEndUpdateBatch",
|
||||
args: [] },
|
||||
];
|
||||
PlacesUtils.tagging.tagURI(uri, [TAG]);
|
||||
PlacesUtils.tagging.untagURI(uri, [TAG]);
|
||||
|
@ -160,6 +160,10 @@ add_task(function pages_searchterm_is_tag_query()
|
||||
compareArrayToResult([], root);
|
||||
gTestData.forEach(function (data) {
|
||||
let uri = NetUtil.newURI(data.uri);
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
uri,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
data.title);
|
||||
PlacesUtils.tagging.tagURI(uri, ["test-tag"]);
|
||||
compareArrayToResult([data], root);
|
||||
PlacesUtils.tagging.untagURI(uri, ["test-tag"]);
|
||||
@ -177,6 +181,10 @@ add_task(function visits_searchterm_is_tag_query()
|
||||
compareArrayToResult([], root);
|
||||
gTestData.forEach(function (data) {
|
||||
let uri = NetUtil.newURI(data.uri);
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
uri,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
data.title);
|
||||
PlacesUtils.tagging.tagURI(uri, ["test-tag"]);
|
||||
compareArrayToResult([data], root);
|
||||
PlacesUtils.tagging.untagURI(uri, ["test-tag"]);
|
||||
|
@ -283,6 +283,7 @@
|
||||
"meta": true,
|
||||
"param": true,
|
||||
"source": true,
|
||||
"wbr": true
|
||||
};
|
||||
|
||||
var whitespace = [" ", "\t", "\n", "\r"];
|
||||
|
@ -109,12 +109,12 @@ Readability.prototype = {
|
||||
DEFAULT_MAX_PAGES: 5,
|
||||
|
||||
// Element tags to score by default.
|
||||
DEFAULT_TAGS_TO_SCORE: ["SECTION", "P", "TD", "PRE"],
|
||||
DEFAULT_TAGS_TO_SCORE: "section,h2,h3,h4,h5,h6,p,td,pre".toUpperCase().split(","),
|
||||
|
||||
// All of the regular expressions in use within readability.
|
||||
// Defined up here so we don't instantiate them repeatedly in loops.
|
||||
REGEXPS: {
|
||||
unlikelyCandidates: /banner|combx|comment|community|disqus|extra|foot|header|menu|remark|rss|share|shoutbox|sidebar|skyscraper|sponsor|ad-break|agegate|pagination|pager|popup/i,
|
||||
unlikelyCandidates: /banner|combx|comment|community|disqus|extra|foot|header|menu|related|remark|rss|share|shoutbox|sidebar|skyscraper|sponsor|ad-break|agegate|pagination|pager|popup/i,
|
||||
okMaybeItsACandidate: /and|article|body|column|main|shadow/i,
|
||||
positive: /article|body|content|entry|hentry|main|page|pagination|post|text|blog|story/i,
|
||||
negative: /hidden|banner|combx|comment|com-|contact|foot|footer|footnote|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i,
|
||||
@ -122,7 +122,7 @@ Readability.prototype = {
|
||||
byline: /byline|author|dateline|writtenby/i,
|
||||
replaceFonts: /<(\/?)font[^>]*>/gi,
|
||||
normalize: /\s{2,}/g,
|
||||
videos: /https?:\/\/(www\.)?(dailymotion|youtube|youtube-nocookie|player\.vimeo)\.com/i,
|
||||
videos: /\/\/(www\.)?(dailymotion|youtube|youtube-nocookie|player\.vimeo)\.com/i,
|
||||
nextLink: /(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i,
|
||||
prevLink: /(prev|earl|old|new|<|«)/i,
|
||||
whitespace: /^\s*$/,
|
||||
@ -739,7 +739,12 @@ Readability.prototype = {
|
||||
candidates.push(ancestor);
|
||||
}
|
||||
|
||||
ancestor.readability.contentScore += contentScore / (level === 0 ? 1 : level * 2);
|
||||
// Node score divider:
|
||||
// - parent: 1 (no division)
|
||||
// - grandparent: 2
|
||||
// - great grandparent+: ancestor level * 3
|
||||
var scoreDivider = level === 0 ? 1 : level === 1 ? 2 : level * 3;
|
||||
ancestor.readability.contentScore += contentScore / scoreDivider;
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -8,6 +8,16 @@ this.EXPORTED_SYMBOLS = ["ReaderMode"];
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
// Constants for telemetry.
|
||||
const DOWNLOAD_SUCCESS = 0;
|
||||
const DOWNLOAD_ERROR_XHR = 1;
|
||||
const DOWNLOAD_ERROR_NO_DOC = 2;
|
||||
|
||||
const PARSE_SUCCESS = 0;
|
||||
const PARSE_ERROR_TOO_MANY_ELEMENTS = 1;
|
||||
const PARSE_ERROR_WORKER = 2;
|
||||
const PARSE_ERROR_NO_ARTICLE = 3;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
@ -17,6 +27,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", "resource://services-comm
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ReaderWorker", "resource://gre/modules/reader/ReaderWorker.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch", "resource://gre/modules/TelemetryStopwatch.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "Readability", function() {
|
||||
let scope = {};
|
||||
@ -169,11 +180,17 @@ this.ReaderMode = {
|
||||
*/
|
||||
downloadAndParseDocument: Task.async(function* (url) {
|
||||
let uri = Services.io.newURI(url, null, null);
|
||||
let doc = yield this._downloadDocument(url);
|
||||
TelemetryStopwatch.start("READER_MODE_DOWNLOAD_MS");
|
||||
let doc = yield this._downloadDocument(url).catch(e => {
|
||||
TelemetryStopwatch.finish("READER_MODE_DOWNLOAD_MS");
|
||||
throw e;
|
||||
});
|
||||
TelemetryStopwatch.finish("READER_MODE_DOWNLOAD_MS");
|
||||
return yield this._readerParse(uri, doc);
|
||||
}),
|
||||
|
||||
_downloadDocument: function (url) {
|
||||
let histogram = Services.telemetry.getHistogramById("READER_MODE_DOWNLOAD_RESULT");
|
||||
return new Promise((resolve, reject) => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url, true);
|
||||
@ -182,12 +199,14 @@ this.ReaderMode = {
|
||||
xhr.onload = evt => {
|
||||
if (xhr.status !== 200) {
|
||||
reject("Reader mode XHR failed with status: " + xhr.status);
|
||||
histogram.add(DOWNLOAD_ERROR_XHR);
|
||||
return;
|
||||
}
|
||||
|
||||
let doc = xhr.responseXML;
|
||||
if (!doc) {
|
||||
reject("Reader mode XHR didn't return a document");
|
||||
histogram.add(DOWNLOAD_ERROR_NO_DOC);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -205,6 +224,7 @@ this.ReaderMode = {
|
||||
}
|
||||
}
|
||||
resolve(doc);
|
||||
histogram.add(DOWNLOAD_SUCCESS);
|
||||
}
|
||||
xhr.send();
|
||||
});
|
||||
@ -292,10 +312,12 @@ this.ReaderMode = {
|
||||
* @resolves JS object representing the article, or null if no article is found.
|
||||
*/
|
||||
_readerParse: Task.async(function* (uri, doc) {
|
||||
let histogram = Services.telemetry.getHistogramById("READER_MODE_PARSE_RESULT");
|
||||
if (this.parseNodeLimit) {
|
||||
let numTags = doc.getElementsByTagName("*").length;
|
||||
if (numTags > this.parseNodeLimit) {
|
||||
this.log("Aborting parse for " + uri.spec + "; " + numTags + " elements found");
|
||||
histogram.add(PARSE_ERROR_TOO_MANY_ELEMENTS);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -308,19 +330,25 @@ this.ReaderMode = {
|
||||
pathBase: Services.io.newURI(".", null, uri).spec
|
||||
};
|
||||
|
||||
TelemetryStopwatch.start("READER_MODE_SERIALIZE_DOM_MS");
|
||||
let serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
|
||||
createInstance(Ci.nsIDOMSerializer);
|
||||
let serializedDoc = yield Promise.resolve(serializer.serializeToString(doc));
|
||||
let serializedDoc = serializer.serializeToString(doc);
|
||||
TelemetryStopwatch.finish("READER_MOD_SERIALIZE_DOM_MS");
|
||||
|
||||
TelemetryStopwatch.start("READER_MODE_WORKER_PARSE_MS");
|
||||
let article = null;
|
||||
try {
|
||||
article = yield ReaderWorker.post("parseDocument", [uriParam, serializedDoc]);
|
||||
} catch (e) {
|
||||
Cu.reportError("Error in ReaderWorker: " + e);
|
||||
histogram.add(PARSE_ERROR_WORKER);
|
||||
}
|
||||
TelemetryStopwatch.finish("READER_MODE_WORKER_PARSE_MS");
|
||||
|
||||
if (!article) {
|
||||
this.log("Worker did not return an article");
|
||||
histogram.add(PARSE_ERROR_NO_ARTICLE);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -331,6 +359,8 @@ this.ReaderMode = {
|
||||
let flags = Ci.nsIDocumentEncoder.OutputSelectionOnly | Ci.nsIDocumentEncoder.OutputAbsoluteLinks;
|
||||
article.title = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils)
|
||||
.convertToPlainText(article.title, flags, 0);
|
||||
|
||||
histogram.add(PARSE_SUCCESS);
|
||||
return article;
|
||||
}),
|
||||
|
||||
|
@ -7793,5 +7793,39 @@
|
||||
"expires_in_version": "50",
|
||||
"kind": "count",
|
||||
"description": "Tracking whether a ServiceWorker spawn gets queued due to hitting max workers per domain limit"
|
||||
},
|
||||
"READER_MODE_SERIALIZE_DOM_MS": {
|
||||
"expires_in_version": "42",
|
||||
"kind": "exponential",
|
||||
"high": "5000",
|
||||
"n_buckets": 15,
|
||||
"description": "Time (ms) to serialize a DOM to send to the reader worker"
|
||||
},
|
||||
"READER_MODE_WORKER_PARSE_MS": {
|
||||
"expires_in_version": "42",
|
||||
"kind": "exponential",
|
||||
"high": "10000",
|
||||
"n_buckets": 30,
|
||||
"description": "Time (ms) for the reader worker to parse a document"
|
||||
},
|
||||
"READER_MODE_DOWNLOAD_MS": {
|
||||
"expires_in_version": "42",
|
||||
"kind": "exponential",
|
||||
"low": 50,
|
||||
"high": "40000",
|
||||
"n_buckets": 60,
|
||||
"description": "Time (ms) to download a document to show in reader mode"
|
||||
},
|
||||
"READER_MODE_PARSE_RESULT" : {
|
||||
"expires_in_version": "42",
|
||||
"kind": "enumerated",
|
||||
"n_values": 5,
|
||||
"description": "The result of trying to parse a document to show in reader view (0=Success, 1=Error too many elements, 2=Error in worker, 3=Error no article)"
|
||||
},
|
||||
"READER_MODE_DOWNLOAD_RESULT" : {
|
||||
"expires_in_version": "42",
|
||||
"kind": "enumerated",
|
||||
"n_values": 5,
|
||||
"description": "The result of trying to download a document to show in reader view (0=Success, 1=Error XHR, 2=Error no document)"
|
||||
}
|
||||
}
|
||||
|
335
toolkit/devtools/server/actors/addon.js
Normal file
335
toolkit/devtools/server/actors/addon.js
Normal file
@ -0,0 +1,335 @@
|
||||
/* 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";
|
||||
|
||||
let { Ci, Cu } = require("chrome");
|
||||
let Services = require("Services");
|
||||
let { ActorPool } = require("devtools/server/actors/common");
|
||||
let { TabSources } = require("./utils/TabSources");
|
||||
let makeDebugger = require("./utils/make-debugger");
|
||||
let { ConsoleAPIListener } = require("devtools/toolkit/webconsole/utils");
|
||||
let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
let { dbg_assert, update } = DevToolsUtils;
|
||||
|
||||
loader.lazyRequireGetter(this, "AddonThreadActor", "devtools/server/actors/script", true);
|
||||
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
|
||||
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
|
||||
loader.lazyRequireGetter(this, "WebConsoleActor", "devtools/server/actors/webconsole", true);
|
||||
|
||||
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
function BrowserAddonActor(aConnection, aAddon) {
|
||||
this.conn = aConnection;
|
||||
this._addon = aAddon;
|
||||
this._contextPool = new ActorPool(this.conn);
|
||||
this.conn.addActorPool(this._contextPool);
|
||||
this._threadActor = null;
|
||||
this._global = null;
|
||||
|
||||
this._shouldAddNewGlobalAsDebuggee = this._shouldAddNewGlobalAsDebuggee.bind(this);
|
||||
|
||||
this.makeDebugger = makeDebugger.bind(null, {
|
||||
findDebuggees: this._findDebuggees.bind(this),
|
||||
shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee
|
||||
});
|
||||
|
||||
AddonManager.addAddonListener(this);
|
||||
}
|
||||
exports.BrowserAddonActor = BrowserAddonActor;
|
||||
|
||||
BrowserAddonActor.prototype = {
|
||||
actorPrefix: "addon",
|
||||
|
||||
get exited() {
|
||||
return !this._addon;
|
||||
},
|
||||
|
||||
get id() {
|
||||
return this._addon.id;
|
||||
},
|
||||
|
||||
get url() {
|
||||
return this._addon.sourceURI ? this._addon.sourceURI.spec : undefined;
|
||||
},
|
||||
|
||||
get attached() {
|
||||
return this._threadActor;
|
||||
},
|
||||
|
||||
get global() {
|
||||
return this._global;
|
||||
},
|
||||
|
||||
get sources() {
|
||||
if (!this._sources) {
|
||||
dbg_assert(this.threadActor, "threadActor should exist when creating sources.");
|
||||
this._sources = new TabSources(this._threadActor, this._allowSource);
|
||||
}
|
||||
return this._sources;
|
||||
},
|
||||
|
||||
|
||||
form: function BAA_form() {
|
||||
dbg_assert(this.actorID, "addon should have an actorID.");
|
||||
if (!this._consoleActor) {
|
||||
this._consoleActor = new AddonConsoleActor(this._addon, this.conn, this);
|
||||
this._contextPool.addActor(this._consoleActor);
|
||||
}
|
||||
|
||||
return {
|
||||
actor: this.actorID,
|
||||
id: this.id,
|
||||
name: this._addon.name,
|
||||
url: this.url,
|
||||
debuggable: this._addon.isDebuggable,
|
||||
consoleActor: this._consoleActor.actorID,
|
||||
|
||||
traits: {
|
||||
highlightable: false,
|
||||
networkMonitor: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
disconnect: function BAA_disconnect() {
|
||||
this.conn.removeActorPool(this._contextPool);
|
||||
this._contextPool = null;
|
||||
this._consoleActor = null;
|
||||
this._addon = null;
|
||||
this._global = null;
|
||||
AddonManager.removeAddonListener(this);
|
||||
},
|
||||
|
||||
setOptions: function BAA_setOptions(aOptions) {
|
||||
if ("global" in aOptions) {
|
||||
this._global = aOptions.global;
|
||||
}
|
||||
},
|
||||
|
||||
onDisabled: function BAA_onDisabled(aAddon) {
|
||||
if (aAddon != this._addon) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._global = null;
|
||||
},
|
||||
|
||||
onUninstalled: function BAA_onUninstalled(aAddon) {
|
||||
if (aAddon != this._addon) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.attached) {
|
||||
this.onDetach();
|
||||
this.conn.send({ from: this.actorID, type: "tabDetached" });
|
||||
}
|
||||
|
||||
this.disconnect();
|
||||
},
|
||||
|
||||
onAttach: function BAA_onAttach() {
|
||||
if (this.exited) {
|
||||
return { type: "exited" };
|
||||
}
|
||||
|
||||
if (!this.attached) {
|
||||
this._threadActor = new AddonThreadActor(this.conn, this);
|
||||
this._contextPool.addActor(this._threadActor);
|
||||
}
|
||||
|
||||
return { type: "tabAttached", threadActor: this._threadActor.actorID };
|
||||
},
|
||||
|
||||
onDetach: function BAA_onDetach() {
|
||||
if (!this.attached) {
|
||||
return { error: "wrongState" };
|
||||
}
|
||||
|
||||
this._contextPool.removeActor(this._threadActor);
|
||||
|
||||
this._threadActor = null;
|
||||
this._sources = null;
|
||||
|
||||
return { type: "detached" };
|
||||
},
|
||||
|
||||
preNest: function() {
|
||||
let e = Services.wm.getEnumerator(null);
|
||||
while (e.hasMoreElements()) {
|
||||
let win = e.getNext();
|
||||
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
windowUtils.suppressEventHandling(true);
|
||||
windowUtils.suspendTimeouts();
|
||||
}
|
||||
},
|
||||
|
||||
postNest: function() {
|
||||
let e = Services.wm.getEnumerator(null);
|
||||
while (e.hasMoreElements()) {
|
||||
let win = e.getNext();
|
||||
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
windowUtils.resumeTimeouts();
|
||||
windowUtils.suppressEventHandling(false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return true if the given global is associated with this addon and should be
|
||||
* added as a debuggee, false otherwise.
|
||||
*/
|
||||
_shouldAddNewGlobalAsDebuggee: function (aGlobal) {
|
||||
const global = unwrapDebuggerObjectGlobal(aGlobal);
|
||||
try {
|
||||
// This will fail for non-Sandbox objects, hence the try-catch block.
|
||||
let metadata = Cu.getSandboxMetadata(global);
|
||||
if (metadata) {
|
||||
return metadata.addonID === this.id;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (global instanceof Ci.nsIDOMWindow) {
|
||||
let id = {};
|
||||
if (mapURIToAddonID(global.document.documentURIObject, id)) {
|
||||
return id.value === this.id;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the global for a __URI__ property and then try to map that to an
|
||||
// add-on
|
||||
let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__");
|
||||
if (uridescriptor && "value" in uridescriptor && uridescriptor.value) {
|
||||
let uri;
|
||||
try {
|
||||
uri = Services.io.newURI(uridescriptor.value, null, null);
|
||||
}
|
||||
catch (e) {
|
||||
DevToolsUtils.reportException(
|
||||
"BrowserAddonActor.prototype._shouldAddNewGlobalAsDebuggee",
|
||||
new Error("Invalid URI: " + uridescriptor.value)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
let id = {};
|
||||
if (mapURIToAddonID(uri, id)) {
|
||||
return id.value === this.id;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Override the eligibility check for scripts and sources to make
|
||||
* sure every script and source with a URL is stored when debugging
|
||||
* add-ons.
|
||||
*/
|
||||
_allowSource: function(aSource) {
|
||||
// XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it.
|
||||
if (aSource.url === "resource://gre/modules/addons/XPIProvider.jsm") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Yield the current set of globals associated with this addon that should be
|
||||
* added as debuggees.
|
||||
*/
|
||||
_findDebuggees: function (dbg) {
|
||||
return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee);
|
||||
}
|
||||
};
|
||||
|
||||
BrowserAddonActor.prototype.requestTypes = {
|
||||
"attach": BrowserAddonActor.prototype.onAttach,
|
||||
"detach": BrowserAddonActor.prototype.onDetach
|
||||
};
|
||||
|
||||
/**
|
||||
* The AddonConsoleActor implements capabilities needed for the add-on web
|
||||
* console feature.
|
||||
*
|
||||
* @constructor
|
||||
* @param object aAddon
|
||||
* The add-on that this console watches.
|
||||
* @param object aConnection
|
||||
* The connection to the client, DebuggerServerConnection.
|
||||
* @param object aParentActor
|
||||
* The parent BrowserAddonActor actor.
|
||||
*/
|
||||
function AddonConsoleActor(aAddon, aConnection, aParentActor)
|
||||
{
|
||||
this.addon = aAddon;
|
||||
WebConsoleActor.call(this, aConnection, aParentActor);
|
||||
}
|
||||
|
||||
AddonConsoleActor.prototype = Object.create(WebConsoleActor.prototype);
|
||||
|
||||
update(AddonConsoleActor.prototype, {
|
||||
constructor: AddonConsoleActor,
|
||||
|
||||
actorPrefix: "addonConsole",
|
||||
|
||||
/**
|
||||
* The add-on that this console watches.
|
||||
*/
|
||||
addon: null,
|
||||
|
||||
/**
|
||||
* The main add-on JS global
|
||||
*/
|
||||
get window() {
|
||||
return this.parentActor.global;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the current AddonConsoleActor instance.
|
||||
*/
|
||||
disconnect: function ACA_disconnect()
|
||||
{
|
||||
WebConsoleActor.prototype.disconnect.call(this);
|
||||
this.addon = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "startListeners" request.
|
||||
*
|
||||
* @param object aRequest
|
||||
* The JSON request object received from the Web Console client.
|
||||
* @return object
|
||||
* The response object which holds the startedListeners array.
|
||||
*/
|
||||
onStartListeners: function ACA_onStartListeners(aRequest)
|
||||
{
|
||||
let startedListeners = [];
|
||||
|
||||
while (aRequest.listeners.length > 0) {
|
||||
let listener = aRequest.listeners.shift();
|
||||
switch (listener) {
|
||||
case "ConsoleAPI":
|
||||
if (!this.consoleAPIListener) {
|
||||
this.consoleAPIListener =
|
||||
new ConsoleAPIListener(null, this, "addon/" + this.addon.id);
|
||||
this.consoleAPIListener.init();
|
||||
}
|
||||
startedListeners.push(listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
startedListeners: startedListeners,
|
||||
nativeConsoleAPI: true,
|
||||
traits: this.traits,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
AddonConsoleActor.prototype.requestTypes = Object.create(WebConsoleActor.prototype.requestTypes);
|
||||
AddonConsoleActor.prototype.requestTypes.startListeners = AddonConsoleActor.prototype.onStartListeners;
|
@ -5303,3 +5303,32 @@ function setBreakpointAtEntryPoints(actor, entryPoints) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap a global that is wrapped in a |Debugger.Object|, or if the global has
|
||||
* become a dead object, return |undefined|.
|
||||
*
|
||||
* @param Debugger.Object wrappedGlobal
|
||||
* The |Debugger.Object| which wraps a global.
|
||||
*
|
||||
* @returns {Object|undefined}
|
||||
* Returns the unwrapped global object or |undefined| if unwrapping
|
||||
* failed.
|
||||
*/
|
||||
exports.unwrapDebuggerObjectGlobal = wrappedGlobal => {
|
||||
try {
|
||||
// Because of bug 991399 we sometimes get nuked window references here. We
|
||||
// just bail out in that case.
|
||||
//
|
||||
// Note that addon sandboxes have a DOMWindow as their prototype. So make
|
||||
// sure that we can touch the prototype too (whatever it is), in case _it_
|
||||
// is it a nuked window reference. We force stringification to make sure
|
||||
// that any dead object proxies make themselves known.
|
||||
let global = wrappedGlobal.unsafeDereference();
|
||||
Object.getPrototypeOf(global) + "";
|
||||
return global;
|
||||
}
|
||||
catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
@ -13,15 +13,15 @@ let { ActorPool, createExtraActors, appendExtraActors } = require("devtools/serv
|
||||
let { DebuggerServer } = require("devtools/server/main");
|
||||
let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
let { dbg_assert } = DevToolsUtils;
|
||||
let { TabSources, isHiddenSource } = require("./utils/TabSources");
|
||||
let { TabSources } = require("./utils/TabSources");
|
||||
let makeDebugger = require("./utils/make-debugger");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
loader.lazyRequireGetter(this, "RootActor", "devtools/server/actors/root", true);
|
||||
loader.lazyRequireGetter(this, "AddonThreadActor", "devtools/server/actors/script", true);
|
||||
loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/script", true);
|
||||
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
|
||||
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
|
||||
loader.lazyRequireGetter(this, "BrowserAddonActor", "devtools/server/actors/addon", true);
|
||||
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
// Assumptions on events module:
|
||||
@ -111,35 +111,6 @@ function sendShutdownEvent() {
|
||||
|
||||
exports.sendShutdownEvent = sendShutdownEvent;
|
||||
|
||||
/**
|
||||
* Unwrap a global that is wrapped in a |Debugger.Object|, or if the global has
|
||||
* become a dead object, return |undefined|.
|
||||
*
|
||||
* @param Debugger.Object wrappedGlobal
|
||||
* The |Debugger.Object| which wraps a global.
|
||||
*
|
||||
* @returns {Object|undefined}
|
||||
* Returns the unwrapped global object or |undefined| if unwrapping
|
||||
* failed.
|
||||
*/
|
||||
const unwrapDebuggerObjectGlobal = wrappedGlobal => {
|
||||
try {
|
||||
// Because of bug 991399 we sometimes get nuked window references here. We
|
||||
// just bail out in that case.
|
||||
//
|
||||
// Note that addon sandboxes have a DOMWindow as their prototype. So make
|
||||
// sure that we can touch the prototype too (whatever it is), in case _it_
|
||||
// is it a nuked window reference. We force stringification to make sure
|
||||
// that any dead object proxies make themselves known.
|
||||
let global = wrappedGlobal.unsafeDereference();
|
||||
Object.getPrototypeOf(global) + "";
|
||||
return global;
|
||||
}
|
||||
catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct a root actor appropriate for use in a server running in a
|
||||
* browser. The returned root actor:
|
||||
@ -2005,238 +1976,6 @@ BrowserAddonList.prototype.onUninstalled = function (aAddon) {
|
||||
|
||||
exports.BrowserAddonList = BrowserAddonList;
|
||||
|
||||
function BrowserAddonActor(aConnection, aAddon) {
|
||||
this.conn = aConnection;
|
||||
this._addon = aAddon;
|
||||
this._contextPool = new ActorPool(this.conn);
|
||||
this.conn.addActorPool(this._contextPool);
|
||||
this._threadActor = null;
|
||||
this._global = null;
|
||||
|
||||
this._shouldAddNewGlobalAsDebuggee = this._shouldAddNewGlobalAsDebuggee.bind(this);
|
||||
|
||||
this.makeDebugger = makeDebugger.bind(null, {
|
||||
findDebuggees: this._findDebuggees.bind(this),
|
||||
shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee
|
||||
});
|
||||
|
||||
AddonManager.addAddonListener(this);
|
||||
}
|
||||
|
||||
BrowserAddonActor.prototype = {
|
||||
actorPrefix: "addon",
|
||||
|
||||
get exited() {
|
||||
return !this._addon;
|
||||
},
|
||||
|
||||
get id() {
|
||||
return this._addon.id;
|
||||
},
|
||||
|
||||
get url() {
|
||||
return this._addon.sourceURI ? this._addon.sourceURI.spec : undefined;
|
||||
},
|
||||
|
||||
get attached() {
|
||||
return this._threadActor;
|
||||
},
|
||||
|
||||
get global() {
|
||||
return this._global;
|
||||
},
|
||||
|
||||
get sources() {
|
||||
if (!this._sources) {
|
||||
dbg_assert(this.threadActor, "threadActor should exist when creating sources.");
|
||||
this._sources = new TabSources(this._threadActor, this._allowSource);
|
||||
}
|
||||
return this._sources;
|
||||
},
|
||||
|
||||
|
||||
form: function BAA_form() {
|
||||
dbg_assert(this.actorID, "addon should have an actorID.");
|
||||
if (!this._consoleActor) {
|
||||
let {AddonConsoleActor} = require("devtools/server/actors/webconsole");
|
||||
this._consoleActor = new AddonConsoleActor(this._addon, this.conn, this);
|
||||
this._contextPool.addActor(this._consoleActor);
|
||||
}
|
||||
|
||||
return {
|
||||
actor: this.actorID,
|
||||
id: this.id,
|
||||
name: this._addon.name,
|
||||
url: this.url,
|
||||
debuggable: this._addon.isDebuggable,
|
||||
consoleActor: this._consoleActor.actorID,
|
||||
|
||||
traits: {
|
||||
highlightable: false,
|
||||
networkMonitor: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
disconnect: function BAA_disconnect() {
|
||||
this.conn.removeActorPool(this._contextPool);
|
||||
this._contextPool = null;
|
||||
this._consoleActor = null;
|
||||
this._addon = null;
|
||||
this._global = null;
|
||||
AddonManager.removeAddonListener(this);
|
||||
},
|
||||
|
||||
setOptions: function BAA_setOptions(aOptions) {
|
||||
if ("global" in aOptions) {
|
||||
this._global = aOptions.global;
|
||||
}
|
||||
},
|
||||
|
||||
onDisabled: function BAA_onDisabled(aAddon) {
|
||||
if (aAddon != this._addon) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._global = null;
|
||||
},
|
||||
|
||||
onUninstalled: function BAA_onUninstalled(aAddon) {
|
||||
if (aAddon != this._addon) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.attached) {
|
||||
this.onDetach();
|
||||
this.conn.send({ from: this.actorID, type: "tabDetached" });
|
||||
}
|
||||
|
||||
this.disconnect();
|
||||
},
|
||||
|
||||
onAttach: function BAA_onAttach() {
|
||||
if (this.exited) {
|
||||
return { type: "exited" };
|
||||
}
|
||||
|
||||
if (!this.attached) {
|
||||
this._threadActor = new AddonThreadActor(this.conn, this);
|
||||
this._contextPool.addActor(this._threadActor);
|
||||
}
|
||||
|
||||
return { type: "tabAttached", threadActor: this._threadActor.actorID };
|
||||
},
|
||||
|
||||
onDetach: function BAA_onDetach() {
|
||||
if (!this.attached) {
|
||||
return { error: "wrongState" };
|
||||
}
|
||||
|
||||
this._contextPool.removeActor(this._threadActor);
|
||||
|
||||
this._threadActor = null;
|
||||
this._sources = null;
|
||||
|
||||
return { type: "detached" };
|
||||
},
|
||||
|
||||
preNest: function() {
|
||||
let e = Services.wm.getEnumerator(null);
|
||||
while (e.hasMoreElements()) {
|
||||
let win = e.getNext();
|
||||
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
windowUtils.suppressEventHandling(true);
|
||||
windowUtils.suspendTimeouts();
|
||||
}
|
||||
},
|
||||
|
||||
postNest: function() {
|
||||
let e = Services.wm.getEnumerator(null);
|
||||
while (e.hasMoreElements()) {
|
||||
let win = e.getNext();
|
||||
let windowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
windowUtils.resumeTimeouts();
|
||||
windowUtils.suppressEventHandling(false);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return true if the given global is associated with this addon and should be
|
||||
* added as a debuggee, false otherwise.
|
||||
*/
|
||||
_shouldAddNewGlobalAsDebuggee: function (aGlobal) {
|
||||
const global = unwrapDebuggerObjectGlobal(aGlobal);
|
||||
try {
|
||||
// This will fail for non-Sandbox objects, hence the try-catch block.
|
||||
let metadata = Cu.getSandboxMetadata(global);
|
||||
if (metadata) {
|
||||
return metadata.addonID === this.id;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (global instanceof Ci.nsIDOMWindow) {
|
||||
let id = {};
|
||||
if (mapURIToAddonID(global.document.documentURIObject, id)) {
|
||||
return id.value === this.id;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the global for a __URI__ property and then try to map that to an
|
||||
// add-on
|
||||
let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__");
|
||||
if (uridescriptor && "value" in uridescriptor && uridescriptor.value) {
|
||||
let uri;
|
||||
try {
|
||||
uri = Services.io.newURI(uridescriptor.value, null, null);
|
||||
}
|
||||
catch (e) {
|
||||
DevToolsUtils.reportException(
|
||||
"BrowserAddonActor.prototype._shouldAddNewGlobalAsDebuggee",
|
||||
new Error("Invalid URI: " + uridescriptor.value)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
let id = {};
|
||||
if (mapURIToAddonID(uri, id)) {
|
||||
return id.value === this.id;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Override the eligibility check for scripts and sources to make
|
||||
* sure every script and source with a URL is stored when debugging
|
||||
* add-ons.
|
||||
*/
|
||||
_allowSource: function(aSource) {
|
||||
// XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it.
|
||||
if (aSource.url === "resource://gre/modules/addons/XPIProvider.jsm") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Yield the current set of globals associated with this addon that should be
|
||||
* added as debuggees.
|
||||
*/
|
||||
_findDebuggees: function (dbg) {
|
||||
return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee);
|
||||
}
|
||||
};
|
||||
|
||||
BrowserAddonActor.prototype.requestTypes = {
|
||||
"attach": BrowserAddonActor.prototype.onAttach,
|
||||
"detach": BrowserAddonActor.prototype.onDetach
|
||||
};
|
||||
|
||||
/**
|
||||
* The DebuggerProgressListener object is an nsIWebProgressListener which
|
||||
* handles onStateChange events for the inspected browser. If the user tries to
|
||||
|
@ -9,7 +9,6 @@
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const { DebuggerServer, ActorPool } = require("devtools/server/main");
|
||||
const { EnvironmentActor, LongStringActor, ObjectActor, ThreadActor } = require("devtools/server/actors/script");
|
||||
const { update } = require("devtools/toolkit/DevToolsUtils");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
@ -735,8 +734,6 @@ WebConsoleActor.prototype =
|
||||
}
|
||||
}
|
||||
|
||||
messages.sort(function(a, b) { return a.timeStamp - b.timeStamp; });
|
||||
|
||||
return {
|
||||
from: this.actorID,
|
||||
messages: messages,
|
||||
@ -1553,91 +1550,6 @@ WebConsoleActor.prototype.requestTypes =
|
||||
|
||||
exports.WebConsoleActor = WebConsoleActor;
|
||||
|
||||
|
||||
/**
|
||||
* The AddonConsoleActor implements capabilities needed for the add-on web
|
||||
* console feature.
|
||||
*
|
||||
* @constructor
|
||||
* @param object aAddon
|
||||
* The add-on that this console watches.
|
||||
* @param object aConnection
|
||||
* The connection to the client, DebuggerServerConnection.
|
||||
* @param object aParentActor
|
||||
* The parent BrowserAddonActor actor.
|
||||
*/
|
||||
function AddonConsoleActor(aAddon, aConnection, aParentActor)
|
||||
{
|
||||
this.addon = aAddon;
|
||||
WebConsoleActor.call(this, aConnection, aParentActor);
|
||||
}
|
||||
|
||||
AddonConsoleActor.prototype = Object.create(WebConsoleActor.prototype);
|
||||
|
||||
update(AddonConsoleActor.prototype, {
|
||||
constructor: AddonConsoleActor,
|
||||
|
||||
actorPrefix: "addonConsole",
|
||||
|
||||
/**
|
||||
* The add-on that this console watches.
|
||||
*/
|
||||
addon: null,
|
||||
|
||||
/**
|
||||
* The main add-on JS global
|
||||
*/
|
||||
get window() {
|
||||
return this.parentActor.global;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the current AddonConsoleActor instance.
|
||||
*/
|
||||
disconnect: function ACA_disconnect()
|
||||
{
|
||||
WebConsoleActor.prototype.disconnect.call(this);
|
||||
this.addon = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "startListeners" request.
|
||||
*
|
||||
* @param object aRequest
|
||||
* The JSON request object received from the Web Console client.
|
||||
* @return object
|
||||
* The response object which holds the startedListeners array.
|
||||
*/
|
||||
onStartListeners: function ACA_onStartListeners(aRequest)
|
||||
{
|
||||
let startedListeners = [];
|
||||
|
||||
while (aRequest.listeners.length > 0) {
|
||||
let listener = aRequest.listeners.shift();
|
||||
switch (listener) {
|
||||
case "ConsoleAPI":
|
||||
if (!this.consoleAPIListener) {
|
||||
this.consoleAPIListener =
|
||||
new ConsoleAPIListener(null, this, "addon/" + this.addon.id);
|
||||
this.consoleAPIListener.init();
|
||||
}
|
||||
startedListeners.push(listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
startedListeners: startedListeners,
|
||||
nativeConsoleAPI: true,
|
||||
traits: this.traits,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
AddonConsoleActor.prototype.requestTypes = Object.create(WebConsoleActor.prototype.requestTypes);
|
||||
AddonConsoleActor.prototype.requestTypes.startListeners = AddonConsoleActor.prototype.onStartListeners;
|
||||
|
||||
exports.AddonConsoleActor = AddonConsoleActor;
|
||||
|
||||
/**
|
||||
* Creates an actor for a network event.
|
||||
*
|
||||
@ -1692,6 +1604,7 @@ NetworkEventActor.prototype =
|
||||
return {
|
||||
actor: this.actorID,
|
||||
startedDateTime: this._startedDateTime,
|
||||
timeStamp: Date.parse(this._startedDateTime),
|
||||
url: this._request.url,
|
||||
method: this._request.method,
|
||||
isXHR: this._isXHR,
|
||||
|
@ -34,6 +34,7 @@ EXTRA_JS_MODULES.devtools.server += [
|
||||
|
||||
EXTRA_JS_MODULES.devtools.server.actors += [
|
||||
'actors/actor-registry.js',
|
||||
'actors/addon.js',
|
||||
'actors/animation.js',
|
||||
'actors/call-watcher.js',
|
||||
'actors/canvas.js',
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
const {Cc, Ci, Cu} = require("chrome");
|
||||
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
loader.lazyImporter(this, "LongStringClient", "resource://gre/modules/devtools/dbg-client.jsm");
|
||||
|
||||
@ -28,11 +29,17 @@ function WebConsoleClient(aDebuggerClient, aResponse)
|
||||
this._longStrings = {};
|
||||
this.traits = aResponse.traits || {};
|
||||
this.events = [];
|
||||
this._networkRequests = new Map();
|
||||
|
||||
this.pendingEvaluationResults = new Map();
|
||||
this.onEvaluationResult = this.onEvaluationResult.bind(this);
|
||||
this.onNetworkEvent = this._onNetworkEvent.bind(this);
|
||||
this.onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
|
||||
|
||||
this._client.addListener("evaluationResult", this.onEvaluationResult);
|
||||
this._client.addListener("networkEvent", this.onNetworkEvent);
|
||||
this._client.addListener("networkEventUpdate", this.onNetworkEventUpdate);
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
exports.WebConsoleClient = WebConsoleClient;
|
||||
@ -41,24 +48,145 @@ WebConsoleClient.prototype = {
|
||||
_longStrings: null,
|
||||
traits: null,
|
||||
|
||||
/**
|
||||
* Holds the network requests currently displayed by the Web Console. Each key
|
||||
* represents the connection ID and the value is network request information.
|
||||
* @private
|
||||
* @type object
|
||||
*/
|
||||
_networkRequests: null,
|
||||
|
||||
getNetworkRequest(actorId) {
|
||||
return this._networkRequests.get(actorId);
|
||||
},
|
||||
|
||||
hasNetworkRequest(actorId) {
|
||||
return this._networkRequests.has(actorId);
|
||||
},
|
||||
|
||||
removeNetworkRequest(actorId) {
|
||||
this._networkRequests.delete(actorId);
|
||||
},
|
||||
|
||||
getNetworkEvents() {
|
||||
return this._networkRequests.values();
|
||||
},
|
||||
|
||||
get actor() { return this._actor; },
|
||||
|
||||
/**
|
||||
* The "networkEvent" message type handler. We redirect any message to
|
||||
* the UI for displaying.
|
||||
*
|
||||
* @private
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object packet
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onNetworkEvent: function (type, packet)
|
||||
{
|
||||
if (packet.from == this._actor) {
|
||||
let actor = packet.eventActor;
|
||||
let networkInfo = {
|
||||
_type: "NetworkEvent",
|
||||
timeStamp: actor.timeStamp,
|
||||
node: null,
|
||||
actor: actor.actor,
|
||||
discardRequestBody: true,
|
||||
discardResponseBody: true,
|
||||
startedDateTime: actor.startedDateTime,
|
||||
request: {
|
||||
url: actor.url,
|
||||
method: actor.method,
|
||||
},
|
||||
isXHR: actor.isXHR,
|
||||
response: {},
|
||||
timings: {},
|
||||
updates: [], // track the list of network event updates
|
||||
private: actor.private,
|
||||
fromCache: actor.fromCache
|
||||
};
|
||||
this._networkRequests.set(actor.actor, networkInfo);
|
||||
|
||||
this.emit("networkEvent", networkInfo);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The "networkEventUpdate" message type handler. We redirect any message to
|
||||
* the UI for displaying.
|
||||
*
|
||||
* @private
|
||||
* @param string type
|
||||
* Message type.
|
||||
* @param object packet
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onNetworkEventUpdate: function (type, packet)
|
||||
{
|
||||
let networkInfo = this.getNetworkRequest(packet.from);
|
||||
if (!networkInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
networkInfo.updates.push(packet.updateType);
|
||||
|
||||
switch (packet.updateType) {
|
||||
case "requestHeaders":
|
||||
networkInfo.request.headersSize = packet.headersSize;
|
||||
break;
|
||||
case "requestPostData":
|
||||
networkInfo.discardRequestBody = packet.discardRequestBody;
|
||||
networkInfo.request.bodySize = packet.dataSize;
|
||||
break;
|
||||
case "responseStart":
|
||||
networkInfo.response.httpVersion = packet.response.httpVersion;
|
||||
networkInfo.response.status = packet.response.status;
|
||||
networkInfo.response.statusText = packet.response.statusText;
|
||||
networkInfo.response.headersSize = packet.response.headersSize;
|
||||
networkInfo.response.remoteAddress = packet.response.remoteAddress;
|
||||
networkInfo.response.remotePort = packet.response.remotePort;
|
||||
networkInfo.discardResponseBody = packet.response.discardResponseBody;
|
||||
break;
|
||||
case "responseContent":
|
||||
networkInfo.response.content = {
|
||||
mimeType: packet.mimeType,
|
||||
};
|
||||
networkInfo.response.bodySize = packet.contentSize;
|
||||
networkInfo.response.transferredSize = packet.transferredSize;
|
||||
networkInfo.discardResponseBody = packet.discardResponseBody;
|
||||
break;
|
||||
case "eventTimings":
|
||||
networkInfo.totalTime = packet.totalTime;
|
||||
break;
|
||||
case "securityInfo":
|
||||
networkInfo.securityInfo = packet.state;
|
||||
break;
|
||||
}
|
||||
|
||||
this.emit("networkEventUpdate", {
|
||||
packet: packet,
|
||||
networkInfo
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the cached messages from the server.
|
||||
*
|
||||
* @see this.CACHED_MESSAGES
|
||||
* @param array aTypes
|
||||
* @param array types
|
||||
* The array of message types you want from the server. See
|
||||
* this.CACHED_MESSAGES for known types.
|
||||
* @param function aOnResponse
|
||||
* The function invoked when the response is received.
|
||||
*/
|
||||
getCachedMessages: function WCC_getCachedMessages(aTypes, aOnResponse)
|
||||
getCachedMessages: function WCC_getCachedMessages(types, aOnResponse)
|
||||
{
|
||||
let packet = {
|
||||
to: this._actor,
|
||||
type: "getCachedMessages",
|
||||
messageTypes: aTypes,
|
||||
messageTypes: types,
|
||||
};
|
||||
this._client.request(packet, aOnResponse);
|
||||
},
|
||||
@ -473,10 +601,18 @@ WebConsoleClient.prototype = {
|
||||
detach: function WCC_detach(aOnResponse)
|
||||
{
|
||||
this._client.removeListener("evaluationResult", this.onEvaluationResult);
|
||||
this._client.removeListener("networkEvent", this.onNetworkEvent);
|
||||
this._client.removeListener("networkEventUpdate", this.onNetworkEventUpdate);
|
||||
this.stopListeners(null, aOnResponse);
|
||||
this._longStrings = null;
|
||||
this._client = null;
|
||||
this.pendingEvaluationResults.clear();
|
||||
this.pendingEvaluationResults = null;
|
||||
this.clearNetworkRequests();
|
||||
this._networkRequests = null;
|
||||
},
|
||||
|
||||
clearNetworkRequests: function () {
|
||||
this._networkRequests.clear();
|
||||
}
|
||||
};
|
||||
|
@ -1496,13 +1496,6 @@ ConsoleAPIListener.prototype =
|
||||
messages = messages.filter((m) => m.consoleID == this.consoleID);
|
||||
}
|
||||
|
||||
// ConsoleAPIStorage gives up messages sorted, but we ask for different
|
||||
// blocks of events and we must sort them again in order to show them in the
|
||||
// proper order.
|
||||
messages = messages.sort(function(a, b) {
|
||||
return a.timeStamp - b.timeStamp;
|
||||
});
|
||||
|
||||
if (aIncludePrivate) {
|
||||
return messages;
|
||||
}
|
||||
|
@ -1357,8 +1357,18 @@ function verifyDirSignedState(aDir, aAddon) {
|
||||
if (!SIGNED_TYPES.has(aAddon.type))
|
||||
return Promise.resolve(undefined);
|
||||
|
||||
// TODO: Get the certificate for an unpacked add-on (bug 1038072)
|
||||
return Promise.resolve(AddonManager.SIGNEDSTATE_MISSING);
|
||||
let certDB = Cc["@mozilla.org/security/x509certdb;1"]
|
||||
.getService(Ci.nsIX509CertDB);
|
||||
|
||||
let root = Ci.nsIX509CertDB.AddonsPublicRoot;
|
||||
if (!REQUIRE_SIGNING && Preferences.get(PREF_XPI_SIGNATURES_DEV_ROOT, false))
|
||||
root = Ci.nsIX509CertDB.AddonsStageRoot;
|
||||
|
||||
return new Promise(resolve => {
|
||||
certDB.verifySignedDirectoryAsync(root, aDir, (aRv, aCert) => {
|
||||
resolve(getSignedStatus(aRv, aCert, aAddon.id));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -236,6 +236,12 @@ fail-if = buildapp == "mulet" || os == "android"
|
||||
[test_pref_properties.js]
|
||||
[test_registry.js]
|
||||
[test_safemode.js]
|
||||
[test_signed_verify.js]
|
||||
[test_signed_inject.js]
|
||||
skip-if = true
|
||||
[test_signed_install.js]
|
||||
run-sequentially = Uses hardcoded ports in xpi files.
|
||||
[test_signed_migrate.js]
|
||||
[test_startup.js]
|
||||
# Bug 676992: test consistently fails on Android
|
||||
fail-if = os == "android"
|
||||
|
@ -22,12 +22,6 @@ skip-if = appname != "firefox"
|
||||
[test_provider_unsafe_access_shutdown.js]
|
||||
[test_provider_unsafe_access_startup.js]
|
||||
[test_shutdown.js]
|
||||
[test_signed_verify.js]
|
||||
[test_signed_inject.js]
|
||||
skip-if = true
|
||||
[test_signed_install.js]
|
||||
run-sequentially = Uses hardcoded ports in xpi files.
|
||||
[test_signed_migrate.js]
|
||||
[test_XPIcancel.js]
|
||||
[test_XPIStates.js]
|
||||
|
||||
|
@ -1641,8 +1641,8 @@
|
||||
|
||||
!macroend
|
||||
|
||||
!define RegisterDLL `!insertmacro RegisterDLL`
|
||||
!define UnregisterDLL `!insertmacro UnregisterDLL`
|
||||
!define RegisterDLL "!insertmacro RegisterDLL"
|
||||
!define UnregisterDLL "!insertmacro UnregisterDLL"
|
||||
|
||||
|
||||
################################################################################
|
||||
@ -4332,24 +4332,21 @@
|
||||
!macroend
|
||||
|
||||
/**
|
||||
* Parses the precomplete file to remove an installation's files and directories.
|
||||
* Parses the precomplete file to remove an installation's files and
|
||||
* directories.
|
||||
*
|
||||
* @param _PROGRESSBAR
|
||||
* The progress bar to update using PBM_STEPIT. Can also be "false" if
|
||||
* updating a progressbar isn't needed.
|
||||
* @param _INSTALL_STEP_COUNTER
|
||||
* The install step counter to increment. The variable specified in
|
||||
* this parameter is also updated. Can also be "false" if a counter
|
||||
* isn't needed.
|
||||
* $R2 = false if all files were deleted or moved to the tobedeleted directory.
|
||||
* @param _CALLBACK
|
||||
* The function address of a callback function for progress or "false"
|
||||
* if there is no callback function.
|
||||
*
|
||||
* $R3 = false if all files were deleted or moved to the tobedeleted directory.
|
||||
* true if file(s) could not be moved to the tobedeleted directory.
|
||||
* $R3 = Path to temporary precomplete file.
|
||||
* $R4 = File handle for the temporary precomplete file.
|
||||
* $R5 = String returned from FileRead.
|
||||
* $R6 = First seven characters of the string returned from FileRead.
|
||||
* $R7 = Temporary file path used to rename files that are in use.
|
||||
* $R8 = _PROGRESSBAR
|
||||
* $R9 = _INSTALL_STEP_COUNTER
|
||||
* $R4 = Path to temporary precomplete file.
|
||||
* $R5 = File handle for the temporary precomplete file.
|
||||
* $R6 = String returned from FileRead.
|
||||
* $R7 = First seven characters of the string returned from FileRead.
|
||||
* $R8 = Temporary file path used to rename files that are in use.
|
||||
* $R9 = _CALLBACK
|
||||
*/
|
||||
!macro RemovePrecompleteEntries
|
||||
|
||||
@ -4368,95 +4365,89 @@
|
||||
|
||||
Function ${_MOZFUNC_UN}RemovePrecompleteEntries
|
||||
Exch $R9
|
||||
Exch 1
|
||||
Exch $R8
|
||||
Push $R8
|
||||
Push $R7
|
||||
Push $R6
|
||||
Push $R5
|
||||
Push $R4
|
||||
Push $R3
|
||||
Push $R2
|
||||
|
||||
${If} ${FileExists} "$INSTDIR\precomplete"
|
||||
StrCpy $R2 "false"
|
||||
StrCpy $R3 "false"
|
||||
|
||||
RmDir /r "$INSTDIR\${TO_BE_DELETED}"
|
||||
CreateDirectory "$INSTDIR\${TO_BE_DELETED}"
|
||||
GetTempFileName $R3 "$INSTDIR\${TO_BE_DELETED}"
|
||||
Delete "$R3"
|
||||
Rename "$INSTDIR\precomplete" "$R3"
|
||||
GetTempFileName $R4 "$INSTDIR\${TO_BE_DELETED}"
|
||||
Delete "$R4"
|
||||
Rename "$INSTDIR\precomplete" "$R4"
|
||||
|
||||
ClearErrors
|
||||
; Rename and then remove files
|
||||
FileOpen $R4 "$R3" r
|
||||
FileOpen $R5 "$R4" r
|
||||
${Do}
|
||||
FileRead $R4 $R5
|
||||
FileRead $R5 $R6
|
||||
${If} ${Errors}
|
||||
${Break}
|
||||
${EndIf}
|
||||
|
||||
${${_MOZFUNC_UN}TrimNewLines} "$R5" $R5
|
||||
${${_MOZFUNC_UN}TrimNewLines} "$R6" $R6
|
||||
; Replace all occurrences of "/" with "\".
|
||||
${${_MOZFUNC_UN}WordReplace} "$R5" "/" "\" "+" $R5
|
||||
${${_MOZFUNC_UN}WordReplace} "$R6" "/" "\" "+" $R6
|
||||
|
||||
; Copy the first 7 chars
|
||||
StrCpy $R6 "$R5" 7
|
||||
${If} "$R6" == "remove "
|
||||
StrCpy $R7 "$R6" 7
|
||||
${If} "$R7" == "remove "
|
||||
; Copy the string starting after the 8th char
|
||||
StrCpy $R5 "$R5" "" 8
|
||||
StrCpy $R6 "$R6" "" 8
|
||||
; Copy all but the last char to remove the double quote.
|
||||
StrCpy $R5 "$R5" -1
|
||||
${If} ${FileExists} "$INSTDIR\$R5"
|
||||
StrCpy $R6 "$R6" -1
|
||||
${If} ${FileExists} "$INSTDIR\$R6"
|
||||
${Unless} "$R9" == "false"
|
||||
IntOp $R9 $R9 + 2
|
||||
${EndUnless}
|
||||
${Unless} "$R8" == "false"
|
||||
SendMessage $R8 ${PBM_STEPIT} 0 0
|
||||
SendMessage $R8 ${PBM_STEPIT} 0 0
|
||||
Call $R9
|
||||
${EndUnless}
|
||||
|
||||
ClearErrors
|
||||
Delete "$INSTDIR\$R5"
|
||||
Delete "$INSTDIR\$R6"
|
||||
${If} ${Errors}
|
||||
GetTempFileName $R7 "$INSTDIR\${TO_BE_DELETED}"
|
||||
Delete "$R7"
|
||||
GetTempFileName $R8 "$INSTDIR\${TO_BE_DELETED}"
|
||||
Delete "$R8"
|
||||
ClearErrors
|
||||
Rename "$INSTDIR\$R5" "$R7"
|
||||
Rename "$INSTDIR\$R6" "$R8"
|
||||
${Unless} ${Errors}
|
||||
Delete /REBOOTOK "$R7"
|
||||
Delete /REBOOTOK "$R8"
|
||||
|
||||
ClearErrors
|
||||
${EndUnless}
|
||||
!ifdef __UNINSTALL__
|
||||
${If} ${Errors}
|
||||
Delete /REBOOTOK "$INSTDIR\$R5"
|
||||
StrCpy $R2 "true"
|
||||
Delete /REBOOTOK "$INSTDIR\$R6"
|
||||
StrCpy $R3 "true"
|
||||
ClearErrors
|
||||
${EndIf}
|
||||
!endif
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
${ElseIf} "$R6" == "rmdir $\""
|
||||
${ElseIf} "$R7" == "rmdir $\""
|
||||
; Copy the string starting after the 7th char.
|
||||
StrCpy $R5 "$R5" "" 7
|
||||
StrCpy $R6 "$R6" "" 7
|
||||
; Copy all but the last two chars to remove the slash and the double quote.
|
||||
StrCpy $R5 "$R5" -2
|
||||
${If} ${FileExists} "$INSTDIR\$R5"
|
||||
StrCpy $R6 "$R6" -2
|
||||
${If} ${FileExists} "$INSTDIR\$R6"
|
||||
; Ignore directory removal errors
|
||||
RmDir "$INSTDIR\$R5"
|
||||
RmDir "$INSTDIR\$R6"
|
||||
ClearErrors
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
${Loop}
|
||||
FileClose $R4
|
||||
FileClose $R5
|
||||
|
||||
; Delete the temporary precomplete file
|
||||
Delete /REBOOTOK "$R3"
|
||||
Delete /REBOOTOK "$R4"
|
||||
|
||||
RmDir /r /REBOOTOK "$INSTDIR\${TO_BE_DELETED}"
|
||||
|
||||
${If} ${RebootFlag}
|
||||
${AndIf} "$R2" == "false"
|
||||
${AndIf} "$R3" == "false"
|
||||
; Clear the reboot flag if all files were deleted or moved to the
|
||||
; tobedeleted directory.
|
||||
SetRebootFlag false
|
||||
@ -4465,14 +4456,12 @@
|
||||
|
||||
ClearErrors
|
||||
|
||||
Pop $R2
|
||||
Pop $R3
|
||||
Pop $R4
|
||||
Pop $R5
|
||||
Pop $R6
|
||||
Pop $R7
|
||||
Exch $R8
|
||||
Exch 1
|
||||
Pop $R8
|
||||
Exch $R9
|
||||
FunctionEnd
|
||||
|
||||
@ -4480,24 +4469,19 @@
|
||||
!endif
|
||||
!macroend
|
||||
|
||||
!macro RemovePrecompleteEntriesCall _PROGRESSBAR _INSTALL_STEP_COUNTER
|
||||
!macro RemovePrecompleteEntriesCall _CALLBACK
|
||||
!verbose push
|
||||
Push "${_PROGRESSBAR}"
|
||||
Push "${_INSTALL_STEP_COUNTER}"
|
||||
Push "${_CALLBACK}"
|
||||
!verbose ${_MOZFUNC_VERBOSE}
|
||||
Call RemovePrecompleteEntries
|
||||
Pop ${_INSTALL_STEP_COUNTER}
|
||||
!verbose pop
|
||||
!macroend
|
||||
|
||||
!macro un.RemovePrecompleteEntriesCall _PROGRESSBAR _INSTALL_STEP_COUNTER
|
||||
!macro un.RemovePrecompleteEntriesCall _CALLBACK
|
||||
!verbose push
|
||||
!verbose ${_MOZFUNC_VERBOSE}
|
||||
Push "${_PROGRESSBAR}"
|
||||
Push "${_INSTALL_STEP_COUNTER}"
|
||||
Push "${_CALLBACK}"
|
||||
Call un.RemovePrecompleteEntries
|
||||
Pop ${_INSTALL_STEP_COUNTER}
|
||||
Pop $0
|
||||
!verbose pop
|
||||
!macroend
|
||||
|
||||
@ -7328,6 +7312,155 @@
|
||||
!endif
|
||||
!macroend
|
||||
|
||||
################################################################################
|
||||
# Helpers for taskbar progress
|
||||
|
||||
!ifndef CLSCTX_INPROC_SERVER
|
||||
!define CLSCTX_INPROC_SERVER 1
|
||||
!endif
|
||||
|
||||
!define CLSID_ITaskbarList {56fdf344-fd6d-11d0-958a-006097c9a090}
|
||||
!define IID_ITaskbarList3 {ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf}
|
||||
!define ITaskbarList3->SetProgressValue $ITaskbarList3->9
|
||||
!define ITaskbarList3->SetProgressState $ITaskbarList3->10
|
||||
|
||||
/**
|
||||
* Creates a single uninitialized object of the ITaskbarList class with a
|
||||
* reference to the ITaskbarList3 interface. This object can be used to set
|
||||
* progress and state on the installer's taskbar icon using the helper macros
|
||||
* in this section.
|
||||
*/
|
||||
!macro ITBL3Create
|
||||
|
||||
!ifndef ${_MOZFUNC_UN}ITBL3Create
|
||||
Var ITaskbarList3
|
||||
|
||||
!verbose push
|
||||
!verbose ${_MOZFUNC_VERBOSE}
|
||||
!define ${_MOZFUNC_UN}ITBL3Create "!insertmacro ${_MOZFUNC_UN}ITBL3CreateCall"
|
||||
|
||||
Function ${_MOZFUNC_UN}ITBL3Create
|
||||
; Setting to 0 allows the helper macros to detect when the object was not
|
||||
; created.
|
||||
StrCpy $ITaskbarList3 0
|
||||
; Don't create when running silently.
|
||||
${Unless} ${Silent}
|
||||
; This is only supported on Win 7 and above.
|
||||
${If} ${AtLeastWin7}
|
||||
System::Call "ole32::CoCreateInstance(g '${CLSID_ITaskbarList}', \
|
||||
i 0, \
|
||||
i ${CLSCTX_INPROC_SERVER}, \
|
||||
g '${IID_ITaskbarList3}', \
|
||||
*i .s)"
|
||||
Pop $ITaskbarList3
|
||||
${EndIf}
|
||||
${EndUnless}
|
||||
FunctionEnd
|
||||
|
||||
!verbose pop
|
||||
!endif
|
||||
!macroend
|
||||
|
||||
!macro ITBL3CreateCall
|
||||
!verbose push
|
||||
!verbose ${_MOZFUNC_VERBOSE}
|
||||
Call ITBL3Create
|
||||
!verbose pop
|
||||
!macroend
|
||||
|
||||
!macro un.ITBL3CreateCall _PATH_TO_IMAGE
|
||||
!verbose push
|
||||
!verbose ${_MOZFUNC_VERBOSE}
|
||||
Call un.ITBL3Create
|
||||
!verbose pop
|
||||
!macroend
|
||||
|
||||
!macro un.ITBL3Create
|
||||
!ifndef un.ITBL3Create
|
||||
!verbose push
|
||||
!verbose ${_MOZFUNC_VERBOSE}
|
||||
!undef _MOZFUNC_UN
|
||||
!define _MOZFUNC_UN "un."
|
||||
|
||||
!insertmacro ITBL3Create
|
||||
|
||||
!undef _MOZFUNC_UN
|
||||
!define _MOZFUNC_UN
|
||||
!verbose pop
|
||||
!endif
|
||||
!macroend
|
||||
|
||||
/**
|
||||
* Sets the percentage completed on the taskbar process icon progress indicator.
|
||||
*
|
||||
* @param _COMPLETED
|
||||
* The proportion of the operation that has been completed in relation
|
||||
* to _TOTAL.
|
||||
* @param _TOTAL
|
||||
* The value _COMPLETED will have when the operation has completed.
|
||||
*
|
||||
* $R8 = _COMPLETED
|
||||
* $R9 = _TOTAL
|
||||
*/
|
||||
!macro ITBL3SetProgressValueCall _COMPLETED _TOTAL
|
||||
Push ${_COMPLETED}
|
||||
Push ${_TOTAL}
|
||||
${CallArtificialFunction} ITBL3SetProgressValue_
|
||||
!macroend
|
||||
|
||||
!define ITBL3SetProgressValue "!insertmacro ITBL3SetProgressValueCall"
|
||||
!define un.ITBL3SetProgressValue "!insertmacro ITBL3SetProgressValueCall"
|
||||
|
||||
!macro ITBL3SetProgressValue_
|
||||
Exch $R9
|
||||
Exch 1
|
||||
Exch $R8
|
||||
${If} ${AtLeastWin7}
|
||||
${AndIf} $ITaskbarList3 <> 0
|
||||
System::Call "${ITaskbarList3->SetProgressValue}(i$HWNDPARENT, l$R8, l$R9)"
|
||||
${EndIf}
|
||||
Exch $R8
|
||||
Exch 1
|
||||
Exch $R9
|
||||
!macroend
|
||||
|
||||
; Normal state / no progress bar
|
||||
!define TBPF_NOPROGRESS 0x00000000
|
||||
; Marquee style progress bar
|
||||
!define TBPF_INDETERMINATE 0x00000001
|
||||
; Standard progress bar
|
||||
!define TBPF_NORMAL 0x00000002
|
||||
; Red taskbar button to indicate an error occurred
|
||||
!define TBPF_ERROR 0x00000004
|
||||
; Yellow taskbar button to indicate user attention (input) is required to
|
||||
; resume progress
|
||||
!define TBPF_PAUSED 0x00000008
|
||||
|
||||
/**
|
||||
* Sets the state on the taskbar process icon progress indicator.
|
||||
*
|
||||
* @param _STATE
|
||||
* The state to set on the taskbar icon progress indicator. Only one of
|
||||
* the states defined above should be specified.
|
||||
*
|
||||
* $R9 = _STATE
|
||||
*/
|
||||
!macro ITBL3SetProgressStateCall _STATE
|
||||
Push ${_STATE}
|
||||
${CallArtificialFunction} ITBL3SetProgressState_
|
||||
!macroend
|
||||
|
||||
!define ITBL3SetProgressState "!insertmacro ITBL3SetProgressStateCall"
|
||||
!define un.ITBL3SetProgressState "!insertmacro ITBL3SetProgressStateCall"
|
||||
|
||||
!macro ITBL3SetProgressState_
|
||||
Exch $R9
|
||||
${If} ${AtLeastWin7}
|
||||
${AndIf} $ITaskbarList3 <> 0
|
||||
System::Call "${ITaskbarList3->SetProgressState}(i$HWNDPARENT, i$R9)"
|
||||
${EndIf}
|
||||
Exch $R9
|
||||
!macroend
|
||||
|
||||
################################################################################
|
||||
# Helpers for the new user interface
|
||||
@ -7430,7 +7563,7 @@
|
||||
Exch $0
|
||||
Pop ${HANDLE}
|
||||
!macroend
|
||||
!define SetStretchedTransparentImage `!insertmacro __SetStretchedTransparentImage`
|
||||
!define SetStretchedTransparentImage "!insertmacro __SetStretchedTransparentImage"
|
||||
|
||||
/**
|
||||
* Removes a single style from a control.
|
||||
|
@ -497,7 +497,7 @@ function hasUpdateMutex() {
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gCanApplyUpdates", function aus_gCanApplyUpdates() {
|
||||
let useService = false;
|
||||
if (shouldUseService() && isServiceInstalled()) {
|
||||
if (shouldUseService()) {
|
||||
// No need to perform directory write checks, the maintenance service will
|
||||
// be able to write to all directories.
|
||||
LOG("gCanApplyUpdates - bypass the write checks because we'll use the service");
|
||||
@ -648,8 +648,7 @@ function getCanStageUpdates() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AppConstants.platform == "win" && isServiceInstalled() &&
|
||||
shouldUseService()) {
|
||||
if (AppConstants.platform == "win" && shouldUseService()) {
|
||||
// No need to perform directory write checks, the maintenance service will
|
||||
// be able to write to all directories.
|
||||
LOG("getCanStageUpdates - able to stage updates using the service");
|
||||
@ -1025,13 +1024,12 @@ function releaseSDCardMountLock() {
|
||||
|
||||
/**
|
||||
* Determines if the service should be used to attempt an update
|
||||
* or not. For now this is only when PREF_APP_UPDATE_SERVICE_ENABLED
|
||||
* is true and we have Firefox.
|
||||
* or not.
|
||||
*
|
||||
* @return true if the service should be used for updates.
|
||||
*/
|
||||
function shouldUseService() {
|
||||
if (AppConstants.MOZ_MAINTENANCE_SERVICE) {
|
||||
if (AppConstants.MOZ_MAINTENANCE_SERVICE && isServiceInstalled()) {
|
||||
return getPref("getBoolPref",
|
||||
PREF_APP_UPDATE_SERVICE_ENABLED, false);
|
||||
}
|
||||
@ -4214,7 +4212,7 @@ Downloader.prototype = {
|
||||
|
||||
if (maxProgress != this._patch.size) {
|
||||
LOG("Downloader:onProgress - maxProgress: " + maxProgress +
|
||||
" is not equal to expectd patch size: " + this._patch.size);
|
||||
" is not equal to expected patch size: " + this._patch.size);
|
||||
// It's important that we use a different code than
|
||||
// NS_ERROR_CORRUPTED_CONTENT so that tests can verify the difference
|
||||
// between a hash error and a wrong download error.
|
||||
|
Loading…
Reference in New Issue
Block a user