зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c.
This commit is contained in:
Коммит
b2624cd00e
|
@ -4115,6 +4115,61 @@
|
|||
event.preventDefault();
|
||||
]]></handler>
|
||||
|
||||
<handler event="click" button="0" phase="capturing"><![CDATA[
|
||||
/* Catches extra clicks meant for the in-tab close button.
|
||||
* Placed here to avoid leaking (a temporary handler added from the
|
||||
* in-tab close button binding would close over the tab and leak it
|
||||
* until the handler itself was removed). (bug 897751)
|
||||
*
|
||||
* The only sequence in which a second click event (i.e. dblclik)
|
||||
* can be dispatched on an in-tab close button is when it is shown
|
||||
* after the first click (i.e. the first click event was dispatched
|
||||
* on the tab). This happens when we show the close button only on
|
||||
* the active tab. (bug 352021)
|
||||
* The only sequence in which a third click event can be dispatched
|
||||
* on an in-tab close button is when the tab was opened with a
|
||||
* double click on the tabbar. (bug 378344)
|
||||
* In both cases, it is most likely that the close button area has
|
||||
* been accidentally clicked, therefore we do not close the tab.
|
||||
*
|
||||
* We don't want to ignore processing of more than one click event,
|
||||
* though, since the user might actually be repeatedly clicking to
|
||||
* close many tabs at once.
|
||||
*/
|
||||
let target = event.originalTarget;
|
||||
if (target.className == 'tab-close-button') {
|
||||
// We preemptively set this to allow the closing-multiple-tabs-
|
||||
// in-a-row case.
|
||||
if (this._blockDblClick) {
|
||||
target._ignoredCloseButtonClicks = true;
|
||||
} else if (event.detail > 1 && !target._ignoredCloseButtonClicks) {
|
||||
target._ignoredCloseButtonClicks = true;
|
||||
event.stopPropagation();
|
||||
return;
|
||||
} else {
|
||||
// Reset the "ignored click" flag
|
||||
target._ignoredCloseButtonClicks = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Protects from close-tab-button errant doubleclick:
|
||||
* Since we're removing the event target, if the user
|
||||
* double-clicks the button, the dblclick event will be dispatched
|
||||
* with the tabbar as its event target (and explicit/originalTarget),
|
||||
* which treats that as a mouse gesture for opening a new tab.
|
||||
* In this context, we're manually blocking the dblclick event
|
||||
* (see tabbrowser-close-tab-button dblclick handler).
|
||||
*/
|
||||
if (this._blockDblClick) {
|
||||
if (!("_clickedTabBarOnce" in this)) {
|
||||
this._clickedTabBarOnce = true;
|
||||
return;
|
||||
}
|
||||
delete this._clickedTabBarOnce;
|
||||
this._blockDblClick = false;
|
||||
}
|
||||
]]></handler>
|
||||
|
||||
<handler event="click"><![CDATA[
|
||||
if (event.button != 1)
|
||||
return;
|
||||
|
@ -4494,53 +4549,10 @@
|
|||
<handler event="click" button="0"><![CDATA[
|
||||
var bindingParent = document.getBindingParent(this);
|
||||
var tabContainer = bindingParent.parentNode;
|
||||
/* The only sequence in which a second click event (i.e. dblclik)
|
||||
* can be dispatched on an in-tab close button is when it is shown
|
||||
* after the first click (i.e. the first click event was dispatched
|
||||
* on the tab). This happens when we show the close button only on
|
||||
* the active tab. (bug 352021)
|
||||
* The only sequence in which a third click event can be dispatched
|
||||
* on an in-tab close button is when the tab was opened with a
|
||||
* double click on the tabbar. (bug 378344)
|
||||
* In both cases, it is most likely that the close button area has
|
||||
* been accidentally clicked, therefore we do not close the tab.
|
||||
*
|
||||
* We don't want to ignore processing of more than one click event,
|
||||
* though, since the user might actually be repeatedly clicking to
|
||||
* close many tabs at once.
|
||||
*/
|
||||
if (event.detail > 1 && !this._ignoredClick) {
|
||||
this._ignoredClick = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the "ignored click" flag
|
||||
this._ignoredClick = false;
|
||||
|
||||
tabContainer.tabbrowser.removeTab(bindingParent, {animate: true, byMouse: true});
|
||||
// This enables double-click protection for the tab container
|
||||
// (see tabbrowser-tabs 'click' handler).
|
||||
tabContainer._blockDblClick = true;
|
||||
|
||||
/* XXXmano hack (see bug 343628):
|
||||
* Since we're removing the event target, if the user
|
||||
* double-clicks this button, the dblclick event will be dispatched
|
||||
* with the tabbar as its event target (and explicit/originalTarget),
|
||||
* which treats that as a mouse gesture for opening a new tab.
|
||||
* In this context, we're manually blocking the dblclick event
|
||||
* (see dblclick handler).
|
||||
*/
|
||||
var clickedOnce = false;
|
||||
function enableDblClick(event) {
|
||||
var target = event.originalTarget;
|
||||
if (target.className == 'tab-close-button')
|
||||
target._ignoredClick = true;
|
||||
if (!clickedOnce) {
|
||||
clickedOnce = true;
|
||||
return;
|
||||
}
|
||||
tabContainer._blockDblClick = false;
|
||||
tabContainer.removeEventListener("click", enableDblClick, true);
|
||||
}
|
||||
tabContainer.addEventListener("click", enableDblClick, true);
|
||||
]]></handler>
|
||||
|
||||
<handler event="dblclick" button="0" phase="capturing">
|
||||
|
|
|
@ -259,7 +259,7 @@ DownloadElementShell.prototype = {
|
|||
}.bind(this),
|
||||
|
||||
function onFailure(aReason) {
|
||||
if (reason instanceof OS.File.Error && reason.becauseNoSuchFile) {
|
||||
if (aReason instanceof OS.File.Error && aReason.becauseNoSuchFile) {
|
||||
this._targetFileInfoFetched = true;
|
||||
this._targetFileExists = false;
|
||||
}
|
||||
|
|
|
@ -34,6 +34,9 @@ function InspectorPanel(iframeWindow, toolbox) {
|
|||
this.panelWin = iframeWindow;
|
||||
this.panelWin.inspector = this;
|
||||
|
||||
this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
|
||||
this._target.on("will-navigate", this._onBeforeNavigate);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
|
@ -144,6 +147,13 @@ InspectorPanel.prototype = {
|
|||
return deferred.promise;
|
||||
},
|
||||
|
||||
_onBeforeNavigate: function() {
|
||||
this._defaultNode = null;
|
||||
this.selection.setNodeFront(null);
|
||||
this._destroyMarkup();
|
||||
this.isDirty = false;
|
||||
},
|
||||
|
||||
_getWalker: function() {
|
||||
let inspector = this.target.inspector;
|
||||
return inspector.getWalker().then(walker => {
|
||||
|
@ -473,6 +483,8 @@ InspectorPanel.prototype = {
|
|||
this.browser = null;
|
||||
}
|
||||
|
||||
this.target.off("will-navigate", this._onBeforeNavigate);
|
||||
|
||||
this.target.off("thread-paused", this.updateDebuggerPausedWarning);
|
||||
this.target.off("thread-resumed", this.updateDebuggerPausedWarning);
|
||||
this._toolbox.off("select", this.updateDebuggerPausedWarning);
|
||||
|
@ -735,15 +747,18 @@ InspectorPanel.prototype = {
|
|||
* Schedule a low-priority change event for things like paint
|
||||
* and resize.
|
||||
*/
|
||||
scheduleLayoutChange: function Inspector_scheduleLayoutChange()
|
||||
scheduleLayoutChange: function Inspector_scheduleLayoutChange(event)
|
||||
{
|
||||
if (this._timer) {
|
||||
return null;
|
||||
// Filter out non browser window resize events (i.e. triggered by iframes)
|
||||
if (this.browser.contentWindow === event.target) {
|
||||
if (this._timer) {
|
||||
return null;
|
||||
}
|
||||
this._timer = this.panelWin.setTimeout(function() {
|
||||
this.emit("layout-change");
|
||||
this._timer = null;
|
||||
}.bind(this), LAYOUT_CHANGE_TIMER);
|
||||
}
|
||||
this._timer = this.panelWin.setTimeout(function() {
|
||||
this.emit("layout-change");
|
||||
this._timer = null;
|
||||
}.bind(this), LAYOUT_CHANGE_TIMER);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -44,3 +44,4 @@ support-files = head.js
|
|||
[browser_inspector_sidebarstate.js]
|
||||
[browser_inspector_bug_848731_reset_selection_on_delete.js]
|
||||
[browser_inspector_bug_848731_reset_selection_on_delete.html]
|
||||
[browser_inspector_bug_922125_destroy_on_navigate.js]
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
let Toolbox = devtools.Toolbox;
|
||||
let TargetFactory = devtools.TargetFactory;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
const URL_1 = "data:text/html;charset=UTF-8,<div id='one' style='color:red;'>ONE</div>";
|
||||
const URL_2 = "data:text/html;charset=UTF-8,<div id='two' style='color:green;'>TWO</div>";
|
||||
|
||||
let inspector;
|
||||
|
||||
// open tab, load URL_1, and wait for load to finish
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let browser = gBrowser.getBrowserForTab(tab);
|
||||
|
||||
function onPageOneLoad() {
|
||||
browser.removeEventListener("load", onPageOneLoad, true);
|
||||
|
||||
gDevTools.showToolbox(target).then(aToolbox => {
|
||||
return aToolbox.selectTool("inspector");
|
||||
}).then(i => {
|
||||
inspector = i;
|
||||
|
||||
// Verify we are on page one
|
||||
let testNode = content.document.querySelector("#one");
|
||||
ok(testNode, "We have the test node on page 1");
|
||||
|
||||
assertMarkupViewIsLoaded();
|
||||
|
||||
// Listen to will-navigate to check if the view is empty
|
||||
target.on("will-navigate", () => {
|
||||
info("Navigation to page 2 has started, the inspector should be empty");
|
||||
assertMarkupViewIsEmpty();
|
||||
});
|
||||
inspector.once("markuploaded", () => {
|
||||
info("Navigation to page 2 was done, the inspector should be back up");
|
||||
|
||||
// Verify we are on page one
|
||||
let testNode = content.document.querySelector("#two");
|
||||
ok(testNode, "We have the test node on page 2");
|
||||
|
||||
// On page 2 load, verify we have the right content
|
||||
assertMarkupViewIsLoaded();
|
||||
endTests();
|
||||
});
|
||||
|
||||
// Navigate to page 2
|
||||
browser.loadURI(URL_2);
|
||||
});
|
||||
}
|
||||
|
||||
// Navigate to page 1
|
||||
browser.addEventListener("load", onPageOneLoad, true);
|
||||
browser.loadURI(URL_1);
|
||||
|
||||
function assertMarkupViewIsLoaded() {
|
||||
let markupViewBox = inspector.panelDoc.getElementById("markup-box");
|
||||
is(markupViewBox.childNodes.length, 1, "The markup-view is loaded");
|
||||
}
|
||||
|
||||
function assertMarkupViewIsEmpty() {
|
||||
let markupViewBox = inspector.panelDoc.getElementById("markup-box");
|
||||
is(markupViewBox.childNodes.length, 0, "The markup-view is unloaded");
|
||||
}
|
||||
|
||||
function endTests() {
|
||||
target = browser = tab = inspector = TargetFactory = Toolbox = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -57,6 +57,10 @@ function test() {
|
|||
inspector.once("computed-view-refreshed", stylePanelAfterChange);
|
||||
|
||||
testDiv.style.fontSize = "15px";
|
||||
|
||||
// FIXME: This shouldn't be needed but as long as we don't fix the bug
|
||||
// where the rule/computed views are not updated when the selected node's
|
||||
// styles change, it has to stay here
|
||||
inspector.emit("layout-change");
|
||||
}
|
||||
|
||||
|
@ -77,6 +81,11 @@ function test() {
|
|||
inspector.once("computed-view-refreshed", stylePanelAfterSwitch);
|
||||
testDiv.style.fontSize = "20px";
|
||||
inspector.sidebar.select("computedview");
|
||||
|
||||
// FIXME: This shouldn't be needed but as long as we don't fix the bug
|
||||
// where the rule/computed views are not updated when the selected node's
|
||||
// styles change, it has to stay here
|
||||
inspector.emit("layout-change");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
|
|||
this.undo = new UndoStack();
|
||||
this.undo.installController(aControllerWindow);
|
||||
|
||||
this._containers = new WeakMap();
|
||||
this._containers = new Map();
|
||||
|
||||
this._boundMutationObserver = this._mutationObserver.bind(this);
|
||||
this.walker.on("mutations", this._boundMutationObserver);
|
||||
|
@ -383,7 +383,6 @@ MarkupView.prototype = {
|
|||
return container;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Mutation observer used for included nodes.
|
||||
*/
|
||||
|
@ -805,6 +804,9 @@ MarkupView.prototype = {
|
|||
|
||||
delete this._elt;
|
||||
|
||||
for ([key, container] of this._containers) {
|
||||
container.destroy();
|
||||
}
|
||||
delete this._containers;
|
||||
},
|
||||
|
||||
|
@ -945,7 +947,8 @@ function MarkupContainer(aMarkupView, aNode) {
|
|||
// Appending the editor element and attaching event listeners
|
||||
this.tagLine.appendChild(this.editor.elt);
|
||||
|
||||
this.elt.addEventListener("mousedown", this._onMouseDown.bind(this), false);
|
||||
this._onMouseDown = this._onMouseDown.bind(this);
|
||||
this.elt.addEventListener("mousedown", this._onMouseDown, false);
|
||||
}
|
||||
|
||||
MarkupContainer.prototype = {
|
||||
|
@ -1164,6 +1167,29 @@ MarkupContainer.prototype = {
|
|||
if (focusable) {
|
||||
focusable.focus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get rid of event listeners and references, when the container is no longer
|
||||
* needed
|
||||
*/
|
||||
destroy: function() {
|
||||
// Recursively destroy children containers
|
||||
let firstChild;
|
||||
while (firstChild = this.children.firstChild) {
|
||||
firstChild.container.destroy();
|
||||
this.children.removeChild(firstChild);
|
||||
}
|
||||
|
||||
// Remove event listeners
|
||||
this.elt.removeEventListener("dblclick", this._onToggle, false);
|
||||
this.elt.removeEventListener("mouseover", this._onMouseOver, false);
|
||||
this.elt.removeEventListener("mouseout", this._onMouseOut, false);
|
||||
this.elt.removeEventListener("mousedown", this._onMouseDown, false);
|
||||
this.expander.removeEventListener("click", this._onToggle, false);
|
||||
|
||||
// Destroy my editor
|
||||
this.editor.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1183,7 +1209,8 @@ function RootContainer(aMarkupView, aNode) {
|
|||
RootContainer.prototype = {
|
||||
hasChildren: true,
|
||||
expanded: true,
|
||||
update: function() {}
|
||||
update: function() {},
|
||||
destroy: function() {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1195,6 +1222,10 @@ function GenericEditor(aContainer, aNode) {
|
|||
this.elt.textContent = aNode.nodeName;
|
||||
}
|
||||
|
||||
GenericEditor.prototype = {
|
||||
destroy: function() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an editor for a DOCTYPE node.
|
||||
*
|
||||
|
@ -1210,6 +1241,10 @@ function DoctypeEditor(aContainer, aNode) {
|
|||
'>';
|
||||
}
|
||||
|
||||
DoctypeEditor.prototype = {
|
||||
destroy: function() {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a simple text editor node, used for TEXT and COMMENT
|
||||
* nodes.
|
||||
|
@ -1284,7 +1319,9 @@ TextEditor.prototype = {
|
|||
}
|
||||
}).then(null, console.error);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1591,7 +1628,9 @@ ElementEditor.prototype = {
|
|||
}
|
||||
});
|
||||
}).then(null, console.error);
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {}
|
||||
};
|
||||
|
||||
function nodeDocument(node) {
|
||||
|
|
|
@ -15,7 +15,7 @@ Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
|||
|
||||
var require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
let {TouchEventHandler} = require("devtools/shared/touch-events");
|
||||
let {TouchEventHandler} = require("devtools/touch-events");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ResponsiveUIManager"];
|
||||
|
||||
|
|
|
@ -270,9 +270,16 @@ CssHtmlTree.prototype = {
|
|||
*/
|
||||
highlight: function(aElement) {
|
||||
if (!aElement) {
|
||||
this.viewedElement = null;
|
||||
this.noResults.hidden = false;
|
||||
|
||||
if (this._refreshProcess) {
|
||||
this._refreshProcess.cancel();
|
||||
}
|
||||
// Hiding all properties
|
||||
for (let propView of this.propertyViews) {
|
||||
propView.refresh();
|
||||
}
|
||||
return promise.resolve(undefined);
|
||||
}
|
||||
|
||||
|
@ -281,8 +288,8 @@ CssHtmlTree.prototype = {
|
|||
}
|
||||
|
||||
this.viewedElement = aElement;
|
||||
|
||||
this.refreshSourceFilter();
|
||||
|
||||
return this.refreshPanel();
|
||||
},
|
||||
|
||||
|
@ -331,6 +338,10 @@ CssHtmlTree.prototype = {
|
|||
*/
|
||||
refreshPanel: function CssHtmlTree_refreshPanel()
|
||||
{
|
||||
if (!this.viewedElement) {
|
||||
return promise.resolve();
|
||||
}
|
||||
|
||||
return promise.all([
|
||||
this._createPropertyViews(),
|
||||
this.pageStyle.getComputed(this.viewedElement, {
|
||||
|
@ -359,8 +370,6 @@ CssHtmlTree.prototype = {
|
|||
// Reset zebra striping.
|
||||
this._darkStripe = true;
|
||||
|
||||
let display = this.propertyContainer.style.display;
|
||||
|
||||
let deferred = promise.defer();
|
||||
this._refreshProcess = new UpdateProcess(this.styleWindow, this.propertyViews, {
|
||||
onItem: (aPropView) => {
|
||||
|
@ -647,12 +656,16 @@ CssHtmlTree.prototype = {
|
|||
// The document in which we display the results (csshtmltree.xul).
|
||||
delete this.styleDocument;
|
||||
|
||||
for (let propView of this.propertyViews) {
|
||||
propView.destroy();
|
||||
}
|
||||
|
||||
// The element that we're inspecting, and the document that it comes from.
|
||||
delete this.propertyViews;
|
||||
delete this.styleWindow;
|
||||
delete this.styleDocument;
|
||||
delete this.styleInspector;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
function PropertyInfo(aTree, aName) {
|
||||
|
@ -761,6 +774,10 @@ PropertyView.prototype = {
|
|||
*/
|
||||
get visible()
|
||||
{
|
||||
if (!this.tree.viewedElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.tree.includeBrowserStyles && !this.hasMatchedSelectors) {
|
||||
return false;
|
||||
}
|
||||
|
@ -809,15 +826,16 @@ PropertyView.prototype = {
|
|||
{
|
||||
let doc = this.tree.styleDocument;
|
||||
|
||||
this.onMatchedToggle = this.onMatchedToggle.bind(this);
|
||||
|
||||
// Build the container element
|
||||
this.element = doc.createElementNS(HTML_NS, "div");
|
||||
this.element.setAttribute("class", this.propertyHeaderClassName);
|
||||
this.element.addEventListener("dblclick",
|
||||
this.onMatchedToggle.bind(this), false);
|
||||
this.element.addEventListener("dblclick", this.onMatchedToggle, false);
|
||||
|
||||
// Make it keyboard navigable
|
||||
this.element.setAttribute("tabindex", "0");
|
||||
this.element.addEventListener("keydown", (aEvent) => {
|
||||
this.onKeyDown = (aEvent) => {
|
||||
let keyEvent = Ci.nsIDOMKeyEvent;
|
||||
if (aEvent.keyCode == keyEvent.DOM_VK_F1) {
|
||||
this.mdnLinkClick();
|
||||
|
@ -826,13 +844,13 @@ PropertyView.prototype = {
|
|||
aEvent.keyCode == keyEvent.DOM_VK_SPACE) {
|
||||
this.onMatchedToggle(aEvent);
|
||||
}
|
||||
}, false);
|
||||
};
|
||||
this.element.addEventListener("keydown", this.onKeyDown, false);
|
||||
|
||||
// Build the twisty expand/collapse
|
||||
this.matchedExpander = doc.createElementNS(HTML_NS, "div");
|
||||
this.matchedExpander.className = "expander theme-twisty";
|
||||
this.matchedExpander.addEventListener("click",
|
||||
this.onMatchedToggle.bind(this), false);
|
||||
this.matchedExpander.addEventListener("click", this.onMatchedToggle, false);
|
||||
this.element.appendChild(this.matchedExpander);
|
||||
|
||||
// Build the style name element
|
||||
|
@ -843,7 +861,8 @@ PropertyView.prototype = {
|
|||
this.nameNode.setAttribute("tabindex", "");
|
||||
this.nameNode.textContent = this.nameNode.title = this.name;
|
||||
// Make it hand over the focus to the container
|
||||
this.nameNode.addEventListener("click", () => this.element.focus(), false);
|
||||
this.onFocus = () => this.element.focus();
|
||||
this.nameNode.addEventListener("click", this.onFocus, false);
|
||||
this.element.appendChild(this.nameNode);
|
||||
|
||||
// Build the style value element
|
||||
|
@ -855,7 +874,7 @@ PropertyView.prototype = {
|
|||
this.valueNode.setAttribute("dir", "ltr");
|
||||
this.valueNode.textContent = this.valueNode.title = this.value;
|
||||
// Make it hand over the focus to the container
|
||||
this.valueNode.addEventListener("click", () => this.element.focus(), false);
|
||||
this.valueNode.addEventListener("click", this.onFocus, false);
|
||||
this.element.appendChild(this.valueNode);
|
||||
|
||||
return this.element;
|
||||
|
@ -981,6 +1000,24 @@ PropertyView.prototype = {
|
|||
}
|
||||
aEvent.preventDefault();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy this property view, removing event listeners
|
||||
*/
|
||||
destroy: function PropertyView_destroy() {
|
||||
this.element.removeEventListener("dblclick", this.onMatchedToggle, false);
|
||||
this.element.removeEventListener("keydown", this.onKeyDown, false);
|
||||
this.element = null;
|
||||
|
||||
this.matchedExpander.removeEventListener("click", this.onMatchedToggle, false);
|
||||
this.matchedExpander = null;
|
||||
|
||||
this.nameNode.removeEventListener("click", this.onFocus, false);
|
||||
this.nameNode = null;
|
||||
|
||||
this.valueNode.removeEventListener("click", this.onFocus, false);
|
||||
this.valueNode = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -76,8 +76,6 @@ function RuleViewTool(aInspector, aWindow, aIFrame)
|
|||
this.refresh = this.refresh.bind(this);
|
||||
this.inspector.on("layout-change", this.refresh);
|
||||
|
||||
this.panelSelected = this.panelSelected.bind(this);
|
||||
this.inspector.sidebar.on("ruleview-selected", this.panelSelected);
|
||||
this.inspector.selection.on("pseudoclass", this.refresh);
|
||||
if (this.inspector.highlighter) {
|
||||
this.inspector.highlighter.on("locked", this._onSelect);
|
||||
|
@ -90,10 +88,6 @@ exports.RuleViewTool = RuleViewTool;
|
|||
|
||||
RuleViewTool.prototype = {
|
||||
onSelect: function RVT_onSelect(aEvent) {
|
||||
if (!this.isActive()) {
|
||||
// We'll update when the panel is selected.
|
||||
return;
|
||||
}
|
||||
this.view.setPageStyle(this.inspector.pageStyle);
|
||||
|
||||
if (!this.inspector.selection.isConnected() ||
|
||||
|
@ -117,27 +111,12 @@ RuleViewTool.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
isActive: function RVT_isActive() {
|
||||
return this.inspector.sidebar.getCurrentTabID() == "ruleview";
|
||||
},
|
||||
|
||||
refresh: function RVT_refresh() {
|
||||
if (this.isActive()) {
|
||||
this.view.nodeChanged();
|
||||
}
|
||||
},
|
||||
|
||||
panelSelected: function() {
|
||||
if (this.inspector.selection.nodeFront === this.view.viewedElement) {
|
||||
this.view.nodeChanged();
|
||||
} else {
|
||||
this.onSelect();
|
||||
}
|
||||
this.view.nodeChanged();
|
||||
},
|
||||
|
||||
destroy: function RVT_destroy() {
|
||||
this.inspector.off("layout-change", this.refresh);
|
||||
this.inspector.sidebar.off("ruleview-selected", this.refresh);
|
||||
this.inspector.selection.off("pseudoclass", this.refresh);
|
||||
this.inspector.selection.off("new-node-front", this._onSelect);
|
||||
if (this.inspector.highlighter) {
|
||||
|
@ -181,8 +160,6 @@ function ComputedViewTool(aInspector, aWindow, aIFrame)
|
|||
this.refresh = this.refresh.bind(this);
|
||||
this.inspector.on("layout-change", this.refresh);
|
||||
this.inspector.selection.on("pseudoclass", this.refresh);
|
||||
this.panelSelected = this.panelSelected.bind(this);
|
||||
this.inspector.sidebar.on("computedview-selected", this.panelSelected);
|
||||
|
||||
this.view.highlight(null);
|
||||
|
||||
|
@ -194,11 +171,6 @@ exports.ComputedViewTool = ComputedViewTool;
|
|||
ComputedViewTool.prototype = {
|
||||
onSelect: function CVT_onSelect(aEvent)
|
||||
{
|
||||
if (!this.isActive()) {
|
||||
// We'll try again when we're selected.
|
||||
return;
|
||||
}
|
||||
|
||||
this.view.setPageStyle(this.inspector.pageStyle);
|
||||
|
||||
if (!this.inspector.selection.isConnected() ||
|
||||
|
@ -226,22 +198,8 @@ ComputedViewTool.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
isActive: function CVT_isActive() {
|
||||
return this.inspector.sidebar.getCurrentTabID() == "computedview";
|
||||
},
|
||||
|
||||
refresh: function CVT_refresh() {
|
||||
if (this.isActive()) {
|
||||
this.view.refreshPanel();
|
||||
}
|
||||
},
|
||||
|
||||
panelSelected: function() {
|
||||
if (this.inspector.selection.nodeFront === this.view.viewedElement) {
|
||||
this.view.refreshPanel();
|
||||
} else {
|
||||
this.onSelect();
|
||||
}
|
||||
this.view.refreshPanel();
|
||||
},
|
||||
|
||||
destroy: function CVT_destroy(aContext)
|
||||
|
|
|
@ -103,7 +103,8 @@ function performWebConsoleTests(hud)
|
|||
is(inspector.selection.node.textContent, "bug653531",
|
||||
"node successfully updated");
|
||||
|
||||
executeSoon(finishTest);
|
||||
gBrowser.removeCurrentTab();
|
||||
finishTest();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import java.lang.reflect.Method;
|
|||
/**
|
||||
* This class covers interactions with the context menu opened from web content
|
||||
*/
|
||||
abstract class ContentContextMenuTest extends BaseTest {
|
||||
abstract class ContentContextMenuTest extends PixelTest {
|
||||
private static final int MAX_TEST_TIMEOUT = 10000;
|
||||
|
||||
// This method opens the context menu of any web content. It assumes that the page is already loaded
|
||||
|
|
|
@ -47,6 +47,22 @@ abstract class PixelTest extends BaseTest {
|
|||
painted.close();
|
||||
}
|
||||
|
||||
public void addTab(String url, String title, boolean isPrivate) {
|
||||
Actions.EventExpecter tabEventExpecter = mActions.expectGeckoEvent("Tab:Added");
|
||||
Actions.EventExpecter contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded");
|
||||
if (isPrivate) {
|
||||
selectMenuItem(StringHelper.NEW_PRIVATE_TAB_LABEL);
|
||||
} else {
|
||||
selectMenuItem(StringHelper.NEW_TAB_LABEL);
|
||||
}
|
||||
tabEventExpecter.blockForEvent();
|
||||
contentEventExpecter.blockForEvent();
|
||||
loadAndPaint(url);
|
||||
tabEventExpecter.unregisterListener();
|
||||
contentEventExpecter.unregisterListener();
|
||||
mAsserter.ok(waitForText(title), "Checking that the page has loaded", "The page has loaded");
|
||||
}
|
||||
|
||||
protected final PaintedSurface waitForPaint(Actions.RepeatedEventExpecter expecter) {
|
||||
expecter.blockUntilClear(PAINT_CLEAR_DELAY);
|
||||
PaintedSurface p = mDriver.getPaintedSurface();
|
||||
|
|
|
@ -17,6 +17,34 @@ class StringHelper {
|
|||
};
|
||||
public static final int DEFAULT_BOOKMARKS_COUNT = DEFAULT_BOOKMARKS_TITLES.length;
|
||||
|
||||
// About pages
|
||||
public static final String ABOUT_BLANK_URL = "about:blank";
|
||||
public static final String ABOUT_FIREFOX_URL = "about:firefox";
|
||||
public static final String ABOUT_DOWNLOADS_URL = "about:downloads";
|
||||
public static final String ABOUT_ADDONS_URL = "about:addons";
|
||||
public static final String ABOUT_APPS_URL = "about:apps";
|
||||
|
||||
// Context Menu menu items
|
||||
public static final String[] CONTEXT_MENU_ITEMS_IN_PRIVATE_TAB = new String[] {
|
||||
"Open Link in Private Tab",
|
||||
"Copy Link",
|
||||
"Share Link",
|
||||
"Bookmark Link"
|
||||
};
|
||||
|
||||
public static final String[] CONTEXT_MENU_ITEMS_IN_NORMAL_TAB = new String[] {
|
||||
"Open Link in New Tab",
|
||||
"Open Link in Private Tab",
|
||||
"Copy Link",
|
||||
"Share Link",
|
||||
"Bookmark Link"
|
||||
};
|
||||
|
||||
public static final String[] BOOKMARKS_OPTIONS_CONTEXTMENU_ITEMS = new String[] {
|
||||
"Edit",
|
||||
"Add to Home Screen"
|
||||
};
|
||||
|
||||
// Robocop page urls
|
||||
// Note: please use getAbsoluteUrl(String url) on each robocop url to get the correct url
|
||||
public static final String ROBOCOP_BIG_LINK_URL = "/robocop/robocop_big_link.html";
|
||||
|
@ -129,10 +157,4 @@ class StringHelper {
|
|||
public static final String BOOKMARK_REMOVED_LABEL = "Bookmark removed";
|
||||
public static final String BOOKMARK_UPDATED_LABEL = "Bookmark updated";
|
||||
public static final String BOOKMARK_OPTIONS_LABEL = "Options";
|
||||
|
||||
// Bookmark Options Context Menu items
|
||||
public static final String[] BOOKMARKS_OPTIONS_CONTEXTMENU_ITEMS = new String[] {
|
||||
"Edit",
|
||||
"Add to Home Screen"
|
||||
};
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
[testImportFromAndroid]
|
||||
[testMasterPassword]
|
||||
[testDeviceSearchEngine]
|
||||
[testPrivateBrowsing]
|
||||
|
||||
# Used for Talos, please don't use in mochitest
|
||||
#[testPan]
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
#filter substitution
|
||||
package @ANDROID_PACKAGE_NAME@.tests;
|
||||
|
||||
import @ANDROID_PACKAGE_NAME@.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* The test loads a new private tab and loads a page with a big link on it
|
||||
* Opens the link in a new private tab and checks that it is private
|
||||
* Adds a new normal tab and loads a 3rd URL
|
||||
* Checks that the bigLinkUrl loaded in the normal tab is present in the browsing history but the 2 urls opened in private tabs are not
|
||||
*/
|
||||
public class testPrivateBrowsing extends ContentContextMenuTest {
|
||||
|
||||
@Override
|
||||
protected int getTestType() {
|
||||
return TEST_MOCHITEST;
|
||||
}
|
||||
|
||||
public void testPrivateBrowsing() {
|
||||
String bigLinkUrl = getAbsoluteUrl(StringHelper.ROBOCOP_BIG_LINK_URL);
|
||||
String blank1Url = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
|
||||
String blank2Url = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_02_URL);
|
||||
|
||||
blockForGeckoReady();
|
||||
|
||||
inputAndLoadUrl(StringHelper.ABOUT_BLANK_URL);
|
||||
|
||||
addTab(bigLinkUrl, StringHelper.ROBOCOP_BIG_LINK_TITLE, true);
|
||||
|
||||
verifyTabCount(1);
|
||||
|
||||
// Open the link context menu and verify the options
|
||||
verifyContextMenuItems(StringHelper.CONTEXT_MENU_ITEMS_IN_PRIVATE_TAB);
|
||||
|
||||
// Check that "Open Link in New Tab" is not in the menu
|
||||
mAsserter.ok(!mSolo.searchText(StringHelper.CONTEXT_MENU_ITEMS_IN_NORMAL_TAB[0]), "Checking that 'Open Link in New Tab' is not displayed in the context menu", "'Open Link in New Tab' is not displayed in the context menu");
|
||||
|
||||
// Open the link in a new private tab and check that it is private
|
||||
Actions.EventExpecter privateTabEventExpector = mActions.expectGeckoEvent("Tab:Added");
|
||||
mSolo.clickOnText(StringHelper.CONTEXT_MENU_ITEMS_IN_PRIVATE_TAB[0]);
|
||||
String eventData = privateTabEventExpector.blockForEventData();
|
||||
privateTabEventExpector.unregisterListener();
|
||||
|
||||
mAsserter.ok(isTabPrivate(eventData), "Checking if the new tab opened from the context menu was a private tab", "The tab was a private tab");
|
||||
verifyTabCount(2);
|
||||
|
||||
// Open a normal tab to check later that it was registered in the Firefox Browser History
|
||||
addTab(blank2Url, StringHelper.ROBOCOP_BLANK_PAGE_02_TITLE, false);
|
||||
verifyTabCount(2);
|
||||
|
||||
// Get the history list and check that the links open in private browsing are not saved
|
||||
ArrayList<String> firefoxHistory = mDatabaseHelper.getBrowserDBUrls(DatabaseHelper.BrowserDataType.HISTORY);
|
||||
mAsserter.ok(!firefoxHistory.contains(bigLinkUrl), "Check that the link opened in the first private tab was not saved", bigLinkUrl + " was not added to history");
|
||||
mAsserter.ok(!firefoxHistory.contains(blank1Url), "Check that the link opened in the private tab from the context menu was not saved", blank1Url + " was not added to history");
|
||||
mAsserter.ok(firefoxHistory.contains(blank2Url), "Check that the link opened in the normal tab was saved", blank2Url + " was added to history");
|
||||
}
|
||||
|
||||
private boolean isTabPrivate(String eventData) {
|
||||
try {
|
||||
JSONObject data = new JSONObject(eventData);
|
||||
return data.getBoolean("isPrivate");
|
||||
} catch (JSONException e) {
|
||||
mAsserter.ok(false, "Error parsing the event data", e.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ interface nsIInputStream;
|
|||
interface nsIFile;
|
||||
interface nsICertificatePrincipal;
|
||||
|
||||
[scriptable, uuid(e1c028bc-c478-11da-95a8-00e08161165f)]
|
||||
[scriptable, uuid(fad6f72f-13d8-4e26-9173-53007a4afe71)]
|
||||
interface nsIZipEntry : nsISupports
|
||||
{
|
||||
/**
|
||||
|
@ -51,6 +51,10 @@ interface nsIZipEntry : nsISupports
|
|||
* It is impossible for a file to be synthetic.
|
||||
*/
|
||||
readonly attribute boolean isSynthetic;
|
||||
/**
|
||||
* The UNIX style file permissions of this item.
|
||||
*/
|
||||
readonly attribute unsigned long permissions;
|
||||
};
|
||||
|
||||
[scriptable, uuid(38d6d07a-8a58-4fe7-be8b-ef6472fa83ff)]
|
||||
|
|
|
@ -911,6 +911,7 @@ nsJARItem::nsJARItem(nsZipItem* aZipItem)
|
|||
mCrc32(aZipItem->CRC32()),
|
||||
mLastModTime(aZipItem->LastModTime()),
|
||||
mCompression(aZipItem->Compression()),
|
||||
mPermissions(aZipItem->Mode()),
|
||||
mIsDirectory(aZipItem->IsDirectory()),
|
||||
mIsSynthetic(aZipItem->isSynthetic)
|
||||
{
|
||||
|
@ -1000,6 +1001,18 @@ nsJARItem::GetLastModifiedTime(PRTime* aLastModTime)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
//------------------------------------------
|
||||
// nsJARItem::GetPermissions
|
||||
//------------------------------------------
|
||||
NS_IMETHODIMP
|
||||
nsJARItem::GetPermissions(uint32_t* aPermissions)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aPermissions);
|
||||
|
||||
*aPermissions = mPermissions;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// nsIZipReaderCache
|
||||
|
||||
|
|
|
@ -142,6 +142,7 @@ private:
|
|||
uint32_t mCrc32;
|
||||
PRTime mLastModTime;
|
||||
uint16_t mCompression;
|
||||
uint32_t mPermissions;
|
||||
bool mIsDirectory;
|
||||
bool mIsSynthetic;
|
||||
};
|
||||
|
|
|
@ -127,6 +127,16 @@ NS_IMETHODIMP nsZipHeader::GetIsSynthetic(bool *aIsSynthetic)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
/* readonly attribute unsigned long permissions; */
|
||||
NS_IMETHODIMP nsZipHeader::GetPermissions(uint32_t *aPermissions)
|
||||
{
|
||||
NS_ASSERTION(mInited, "Not initalised");
|
||||
|
||||
// Always give user read access at least, this matches nsIZipReader's behaviour
|
||||
*aPermissions = ((mEAttr >> 16) & 0xfff | 0x100);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsZipHeader::Init(const nsACString & aPath, PRTime aDate, uint32_t aAttr,
|
||||
uint32_t aOffset)
|
||||
{
|
||||
|
|
|
@ -10,7 +10,7 @@ var TESTS = [];
|
|||
function build_tests() {
|
||||
var id = 0;
|
||||
|
||||
// Minimum mode is 0400
|
||||
// Minimum mode is 0o400
|
||||
for (let u = 4; u <= 7; u++) {
|
||||
for (let g = 0; g <= 7; g++) {
|
||||
for (let o = 0; o <= 7; o++) {
|
||||
|
@ -53,18 +53,28 @@ function run_test() {
|
|||
}
|
||||
|
||||
zipW.addEntryFile(TESTS[i].name, Ci.nsIZipWriter.COMPRESSION_NONE, file, false);
|
||||
file.permissions = 0600;
|
||||
do_check_eq(zipW.getEntry(TESTS[i].name).permissions, TESTS[i].permission | 0o400);
|
||||
file.permissions = 0o600;
|
||||
file.remove(true);
|
||||
}
|
||||
zipW.close();
|
||||
|
||||
zipW.open(tmpFile, PR_RDWR);
|
||||
for (let i = 0; i < TESTS.length; i++) {
|
||||
dump("Testing zipwriter file permissions for " + TESTS[i].name + "\n");
|
||||
do_check_eq(zipW.getEntry(TESTS[i].name).permissions, TESTS[i].permission | 0o400);
|
||||
}
|
||||
zipW.close();
|
||||
|
||||
var zipR = new ZipReader(tmpFile);
|
||||
for (let i = 0; i < TESTS.length; i++) {
|
||||
dump("Testing zipreader file permissions for " + TESTS[i].name + "\n");
|
||||
do_check_eq(zipR.getEntry(TESTS[i].name).permissions, TESTS[i].permission | 0o400);
|
||||
dump("Testing extracted file permissions for " + TESTS[i].name + "\n");
|
||||
zipR.extract(TESTS[i].name, file);
|
||||
dump("Testing file permissions for " + TESTS[i].name + "\n");
|
||||
do_check_eq(file.permissions & 0xfff, TESTS[i].permission);
|
||||
do_check_false(file.isDirectory());
|
||||
file.permissions = 0600;
|
||||
file.permissions = 0o600;
|
||||
file.remove(true);
|
||||
}
|
||||
zipR.close();
|
||||
|
|
|
@ -208,9 +208,16 @@ let Scheduler = {
|
|||
// Decode any serialized error
|
||||
if (error instanceof PromiseWorker.WorkerError) {
|
||||
throw OS.File.Error.fromMsg(error.data);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
// Extract something meaningful from WorkerErrorEvent
|
||||
if (typeof error == "object" && error && error.constructor.name == "WorkerErrorEvent") {
|
||||
let message = error.message;
|
||||
if (message == "uncaught exception: [object StopIteration]") {
|
||||
throw StopIteration;
|
||||
}
|
||||
throw new Error(message, error.filename, error.lineno);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -965,14 +972,11 @@ DirectoryIterator.prototype = {
|
|||
promise = promise.then(
|
||||
DirectoryIterator.Entry.fromMsg,
|
||||
function onReject(reason) {
|
||||
// If the exception is |StopIteration| (which we may determine only
|
||||
// from its message...) we need to stop the iteration.
|
||||
if (!(reason instanceof WorkerErrorEvent && reason.message == "uncaught exception: [object StopIteration]")) {
|
||||
// Any exception other than StopIteration should be propagated as such
|
||||
throw reason;
|
||||
if (reason == StopIteration) {
|
||||
self.close();
|
||||
throw StopIteration;
|
||||
}
|
||||
self.close();
|
||||
throw StopIteration;
|
||||
throw reason;
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
|
||||
function run_test() {
|
||||
do_test_pending();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function test_typeerror() {
|
||||
let exn;
|
||||
try {
|
||||
let fd = yield OS.File.open("/tmp", {no_such_key: 1});
|
||||
do_print("Fd: " + fd);
|
||||
} catch (ex) {
|
||||
exn = ex;
|
||||
}
|
||||
do_print("Exception: " + exn);
|
||||
do_check_true(typeof exn == "object");
|
||||
do_check_true("name" in exn);
|
||||
do_check_true(exn.message.indexOf("TypeError") != -1);
|
||||
});
|
||||
|
||||
add_task(function() {
|
||||
do_test_finished();
|
||||
});
|
|
@ -8,5 +8,6 @@ tail =
|
|||
[test_profiledir.js]
|
||||
[test_logging.js]
|
||||
[test_creationDate.js]
|
||||
[test_exception.js]
|
||||
[test_path_constants.js]
|
||||
[test_removeDir.js]
|
||||
|
|
|
@ -137,6 +137,7 @@ nsAppStartup::nsAppStartup() :
|
|||
mConsiderQuitStopper(0),
|
||||
mRunning(false),
|
||||
mShuttingDown(false),
|
||||
mStartingUp(true),
|
||||
mAttemptingQuit(false),
|
||||
mRestart(false),
|
||||
mInterrupted(false),
|
||||
|
@ -516,6 +517,23 @@ nsAppStartup::GetShuttingDown(bool *aResult)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAppStartup::GetStartingUp(bool *aResult)
|
||||
{
|
||||
*aResult = mStartingUp;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAppStartup::DoneStartingUp()
|
||||
{
|
||||
// This must be called once at most
|
||||
MOZ_ASSERT(mStartingUp);
|
||||
|
||||
mStartingUp = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsAppStartup::GetRestarting(bool *aResult)
|
||||
{
|
||||
|
|
|
@ -56,6 +56,7 @@ private:
|
|||
int32_t mConsiderQuitStopper; // if > 0, Quit(eConsiderQuit) fails
|
||||
bool mRunning; // Have we started the main event loop?
|
||||
bool mShuttingDown; // Quit method reentrancy check
|
||||
bool mStartingUp; // Have we passed final-ui-startup?
|
||||
bool mAttemptingQuit; // Quit(eAttemptQuit) still trying
|
||||
bool mRestart; // Quit(eRestart)
|
||||
bool mInterrupted; // Was startup interrupted by an interactive prompt?
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
interface nsICmdLineService;
|
||||
|
||||
[scriptable, uuid(744d6ec0-115f-11e3-9c94-68fd99890b3c)]
|
||||
[scriptable, uuid(9edef217-e664-4938-85a7-2fe84baa1755)]
|
||||
interface nsIAppStartup : nsISupports
|
||||
{
|
||||
/**
|
||||
|
@ -135,6 +135,20 @@ interface nsIAppStartup : nsISupports
|
|||
*/
|
||||
readonly attribute boolean shuttingDown;
|
||||
|
||||
/**
|
||||
* True if the application is in the process of starting up.
|
||||
*
|
||||
* Startup is complete once all observers of final-ui-startup have returned.
|
||||
*/
|
||||
readonly attribute boolean startingUp;
|
||||
|
||||
/**
|
||||
* Mark the startup as completed.
|
||||
*
|
||||
* Called at the end of startup by nsAppRunner.
|
||||
*/
|
||||
[noscript] void doneStartingUp();
|
||||
|
||||
/**
|
||||
* True if the application is being restarted
|
||||
*/
|
||||
|
|
|
@ -62,6 +62,8 @@ XPCOMUtils.defineLazyServiceGetter(this, "idleService",
|
|||
"nsIIdleService");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
|
||||
"resource://gre/modules/UpdateChannel.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
|
||||
"resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
function generateUUID() {
|
||||
let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString();
|
||||
|
@ -158,9 +160,7 @@ TelemetryPing.prototype = {
|
|||
appTimestamps = o.TelemetryTimestamps.get();
|
||||
} catch (ex) {}
|
||||
try {
|
||||
let o = {};
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm", o);
|
||||
ret.addonManager = o.AddonManagerPrivate.getSimpleMeasures();
|
||||
ret.addonManager = AddonManagerPrivate.getSimpleMeasures();
|
||||
} catch (ex) {}
|
||||
|
||||
if (si.process) {
|
||||
|
@ -545,6 +545,7 @@ TelemetryPing.prototype = {
|
|||
chromeHangs: Telemetry.chromeHangs,
|
||||
lateWrites: Telemetry.lateWrites,
|
||||
addonHistograms: this.getAddonHistograms(),
|
||||
addonDetails: AddonManagerPrivate.getTelemetryDetails(),
|
||||
info: info
|
||||
};
|
||||
|
||||
|
@ -689,7 +690,7 @@ TelemetryPing.prototype = {
|
|||
let observer = {
|
||||
buffer: "",
|
||||
onStreamComplete: function(loader, context, status, length, result) {
|
||||
this.buffer = String.fromCharCode.apply(this, result);
|
||||
this.buffer = String.fromCharCode.apply(this, result);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -523,19 +523,48 @@ let Histogram = {
|
|||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Helper function to render JS objects with white space between top level elements
|
||||
* so that they look better in the browser
|
||||
* @param aObject JavaScript object or array to render
|
||||
* @return String
|
||||
*/
|
||||
function RenderObject(aObject) {
|
||||
let output = "";
|
||||
if (Array.isArray(aObject)) {
|
||||
if (aObject.length == 0) {
|
||||
return "[]";
|
||||
}
|
||||
output = "[" + JSON.stringify(aObject[0]);
|
||||
for (let i = 1; i < aObject.length; i++) {
|
||||
output += ", " + JSON.stringify(aObject[i]);
|
||||
}
|
||||
return output + "]";
|
||||
}
|
||||
let keys = Object.keys(aObject);
|
||||
if (keys.length == 0) {
|
||||
return "{}";
|
||||
}
|
||||
output = "{\"" + keys[0] + "\":\u00A0" + JSON.stringify(aObject[keys[0]]);
|
||||
for (let i = 1; i < keys.length; i++) {
|
||||
output += ", \"" + keys[i] + "\":\u00A0" + JSON.stringify(aObject[keys[i]]);
|
||||
}
|
||||
return output + "}";
|
||||
};
|
||||
|
||||
let KeyValueTable = {
|
||||
|
||||
keysHeader: bundle.GetStringFromName("keysHeader"),
|
||||
|
||||
valuesHeader: bundle.GetStringFromName("valuesHeader"),
|
||||
|
||||
/**
|
||||
* Fill out a 2-column table with keys and values
|
||||
* Returns a 2-column table with keys and values
|
||||
* @param aMeasurements Each key in this JS object is rendered as a row in
|
||||
* the table with its corresponding value
|
||||
* @param aKeysLabel Column header for the keys column
|
||||
* @param aValuesLabel Column header for the values column
|
||||
*/
|
||||
render: function KeyValueTable_render(aTableID, aMeasurements) {
|
||||
let table = document.getElementById(aTableID);
|
||||
this.renderHeader(table);
|
||||
render: function KeyValueTable_render(aMeasurements, aKeysLabel, aValuesLabel) {
|
||||
let table = document.createElement("table");
|
||||
this.renderHeader(table, aKeysLabel, aValuesLabel);
|
||||
this.renderBody(table, aMeasurements);
|
||||
return table;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -543,15 +572,17 @@ let KeyValueTable = {
|
|||
* Tabs & newlines added to cells to make it easier to copy-paste.
|
||||
*
|
||||
* @param aTable Table element
|
||||
* @param aKeysLabel Column header for the keys column
|
||||
* @param aValuesLabel Column header for the values column
|
||||
*/
|
||||
renderHeader: function KeyValueTable_renderHeader(aTable) {
|
||||
renderHeader: function KeyValueTable_renderHeader(aTable, aKeysLabel, aValuesLabel) {
|
||||
let headerRow = document.createElement("tr");
|
||||
aTable.appendChild(headerRow);
|
||||
|
||||
let keysColumn = document.createElement("th");
|
||||
keysColumn.appendChild(document.createTextNode(this.keysHeader + "\t"));
|
||||
keysColumn.appendChild(document.createTextNode(aKeysLabel + "\t"));
|
||||
let valuesColumn = document.createElement("th");
|
||||
valuesColumn.appendChild(document.createTextNode(this.valuesHeader + "\n"));
|
||||
valuesColumn.appendChild(document.createTextNode(aValuesLabel + "\n"));
|
||||
|
||||
headerRow.appendChild(keysColumn);
|
||||
headerRow.appendChild(valuesColumn);
|
||||
|
@ -567,7 +598,7 @@ let KeyValueTable = {
|
|||
renderBody: function KeyValueTable_renderBody(aTable, aMeasurements) {
|
||||
for (let [key, value] of Iterator(aMeasurements)) {
|
||||
if (typeof value == "object") {
|
||||
value = JSON.stringify(value);
|
||||
value = RenderObject(value);
|
||||
}
|
||||
|
||||
let newRow = document.createElement("tr");
|
||||
|
@ -584,6 +615,28 @@ let KeyValueTable = {
|
|||
}
|
||||
};
|
||||
|
||||
let AddonDetails = {
|
||||
tableIDTitle: bundle.GetStringFromName("addonTableID"),
|
||||
tableDetailsTitle: bundle.GetStringFromName("addonTableDetails"),
|
||||
|
||||
/**
|
||||
* Render the addon details section as a series of headers followed by key/value tables
|
||||
* @param aSections Object containing the details sections to render
|
||||
*/
|
||||
render: function AddonDetails_render(aSections) {
|
||||
let addonSection = document.getElementById("addon-details");
|
||||
for (let provider in aSections) {
|
||||
let providerSection = document.createElement("h2");
|
||||
let titleText = bundle.formatStringFromName("addonProvider", [provider], 1);
|
||||
providerSection.appendChild(document.createTextNode(titleText));
|
||||
addonSection.appendChild(providerSection);
|
||||
addonSection.appendChild(
|
||||
KeyValueTable.render(aSections[provider],
|
||||
this.tableIDTitle, this.tableDetailsTitle));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function for showing "No data collected" message for a section
|
||||
*
|
||||
|
@ -813,10 +866,15 @@ function sortStartupMilestones(aSimpleMeasurements) {
|
|||
function displayPingData() {
|
||||
let ping = TelemetryPing.getPayload();
|
||||
|
||||
let keysHeader = bundle.GetStringFromName("keysHeader");
|
||||
let valuesHeader = bundle.GetStringFromName("valuesHeader");
|
||||
|
||||
// Show simple measurements
|
||||
let simpleMeasurements = sortStartupMilestones(ping.simpleMeasurements);
|
||||
if (Object.keys(simpleMeasurements).length) {
|
||||
KeyValueTable.render("simple-measurements-table", simpleMeasurements);
|
||||
let simpleSection = document.getElementById("simple-measurements");
|
||||
simpleSection.appendChild(KeyValueTable.render(simpleMeasurements,
|
||||
keysHeader, valuesHeader));
|
||||
} else {
|
||||
showEmptySectionMessage("simple-measurements-section");
|
||||
}
|
||||
|
@ -825,10 +883,19 @@ function displayPingData() {
|
|||
|
||||
// Show basic system info gathered
|
||||
if (Object.keys(ping.info).length) {
|
||||
KeyValueTable.render("system-info-table", ping.info);
|
||||
let infoSection = document.getElementById("system-info");
|
||||
infoSection.appendChild(KeyValueTable.render(ping.info,
|
||||
keysHeader, valuesHeader));
|
||||
} else {
|
||||
showEmptySectionMessage("system-info-section");
|
||||
}
|
||||
|
||||
let addonDetails = ping.addonDetails;
|
||||
if (Object.keys(addonDetails).length) {
|
||||
AddonDetails.render(addonDetails);
|
||||
} else {
|
||||
showEmptySectionMessage("addon-details-section");
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("load", onLoad, false);
|
||||
|
|
|
@ -75,8 +75,6 @@
|
|||
<span class="toggle-caption hidden">&aboutTelemetry.toggleOff;</span>
|
||||
<span class="empty-caption hidden">&aboutTelemetry.emptySection;</span>
|
||||
<div id="simple-measurements" class="data hidden">
|
||||
<table id="simple-measurements-table">
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
@ -101,8 +99,15 @@
|
|||
<span class="toggle-caption hidden">&aboutTelemetry.toggleOff;</span>
|
||||
<span class="empty-caption hidden">&aboutTelemetry.emptySection;</span>
|
||||
<div id="system-info" class="data hidden">
|
||||
<table id="system-info-table">
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="addon-details-section" class="data-section">
|
||||
<h1 class="section-name">&aboutTelemetry.addonDetailsSection;</h1>
|
||||
<span class="toggle-caption">&aboutTelemetry.toggleOn;</span>
|
||||
<span class="toggle-caption hidden">&aboutTelemetry.toggleOff;</span>
|
||||
<span class="empty-caption hidden">&aboutTelemetry.emptySection;</span>
|
||||
<div id="addon-details" class="data hidden">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ var BuiltinProvider = {
|
|||
"devtools/app-actor-front": "resource://gre/modules/devtools/app-actor-front.js",
|
||||
"devtools/styleinspector/css-logic": "resource://gre/modules/devtools/styleinspector/css-logic",
|
||||
"devtools/css-color": "resource://gre/modules/devtools/css-color",
|
||||
"devtools/touch-events": "resource://gre/modules/devtools/touch-events",
|
||||
"devtools/client": "resource://gre/modules/devtools/client",
|
||||
|
||||
"escodegen": "resource://gre/modules/devtools/escodegen",
|
||||
|
@ -102,6 +103,7 @@ var SrcdirProvider = {
|
|||
let appActorURI = this.fileURI(OS.Path.join(toolkitDir, "apps", "app-actor-front.js"));
|
||||
let cssLogicURI = this.fileURI(OS.Path.join(toolkitDir, "styleinspector", "css-logic"));
|
||||
let cssColorURI = this.fileURI(OS.Path.join(toolkitDir, "css-color"));
|
||||
let touchEventsURI = this.fileURI(OS.Path.join(toolkitDir, "touch-events"));
|
||||
let clientURI = this.fileURI(OS.Path.join(toolkitDir, "client"));
|
||||
let escodegenURI = this.fileURI(OS.Path.join(toolkitDir, "escodegen"));
|
||||
let estraverseURI = this.fileURI(OS.Path.join(toolkitDir, "escodegen", "estraverse"));
|
||||
|
@ -118,7 +120,7 @@ var SrcdirProvider = {
|
|||
"devtools/toolkit/webconsole": webconsoleURI,
|
||||
"devtools/app-actor-front": appActorURI,
|
||||
"devtools/styleinspector/css-logic": cssLogicURI,
|
||||
"devtools/css-color": cssColorURI,
|
||||
"devtools/touch-events": touchEventsURI,
|
||||
"devtools/client": clientURI,
|
||||
"escodegen": escodegenURI,
|
||||
"estraverse": estraverseURI
|
||||
|
|
|
@ -1325,11 +1325,13 @@ var WalkerActor = protocol.ActorClass({
|
|||
* @param string selector
|
||||
*/
|
||||
querySelector: method(function(baseNode, selector) {
|
||||
if (!baseNode) {
|
||||
return {}
|
||||
};
|
||||
let node = baseNode.rawNode.querySelector(selector);
|
||||
|
||||
if (!node) {
|
||||
return {
|
||||
}
|
||||
return {}
|
||||
};
|
||||
|
||||
let node = this._ref(node);
|
||||
|
|
|
@ -136,12 +136,11 @@ var PageStyleActor = protocol.ActorClass({
|
|||
* }
|
||||
*/
|
||||
getComputed: method(function(node, options) {
|
||||
let win = node.rawNode.ownerDocument.defaultView;
|
||||
let ret = Object.create(null);
|
||||
|
||||
this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA;
|
||||
this.cssLogic.highlight(node.rawNode);
|
||||
let computed = this.cssLogic._computedStyle;
|
||||
let computed = this.cssLogic._computedStyle || [];
|
||||
|
||||
Array.prototype.forEach.call(computed, name => {
|
||||
let matched = undefined;
|
||||
|
|
|
@ -381,8 +381,19 @@ CssLogic.prototype = {
|
|||
*/
|
||||
forEachSheet: function CssLogic_forEachSheet(aCallback, aScope)
|
||||
{
|
||||
for each (let sheet in this._sheets) {
|
||||
sheet.forEach(aCallback, aScope);
|
||||
for each (let sheets in this._sheets) {
|
||||
for (let i = 0; i < sheets.length; i ++) {
|
||||
// We take this as an opportunity to clean dead sheets
|
||||
try {
|
||||
let sheet = sheets[i];
|
||||
sheet.domSheet; // If accessing domSheet raises an exception, then the
|
||||
// style sheet is a dead object
|
||||
aCallback.call(aScope, sheet, i, sheets);
|
||||
} catch (e) {
|
||||
sheets.splice(i, 1);
|
||||
i --;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1008,8 +1019,8 @@ CssSheet.prototype = {
|
|||
get ruleCount()
|
||||
{
|
||||
return this._ruleCount > -1 ?
|
||||
this._ruleCount :
|
||||
this.domSheet.cssRules.length;
|
||||
this._ruleCount :
|
||||
this.domSheet.cssRules.length;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1117,7 +1128,7 @@ CssSheet.prototype = {
|
|||
toString: function CssSheet_toString()
|
||||
{
|
||||
return "CssSheet[" + this.shortSource + "]";
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,6 +28,10 @@
|
|||
Simple Measurements
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.addonDetailsSection "
|
||||
Add-on Details
|
||||
">
|
||||
|
||||
<!ENTITY aboutTelemetry.lateWritesSection "
|
||||
Late Writes
|
||||
">
|
||||
|
|
|
@ -45,3 +45,11 @@ enableTelemetry = Enable Telemetry
|
|||
keysHeader = Property
|
||||
|
||||
valuesHeader = Value
|
||||
|
||||
addonTableID = Add-on ID
|
||||
|
||||
addonTableDetails = Details
|
||||
|
||||
# Note to translators:
|
||||
# - The %1$S will be replaced with the name of an Add-on Provider (e.g. "XPI", "Plugin")
|
||||
addonProvider = %1$S Provider
|
||||
|
|
|
@ -16,6 +16,8 @@ Cu.import("resource://gre/modules/Services.jsm");
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
|
||||
"resource://gre/modules/AsyncShutdown.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
|
||||
"resource://services-common/utils.js");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
|
@ -197,6 +199,7 @@ function OpenedConnection(connection, basename, number, options) {
|
|||
this._log.info("Opened");
|
||||
|
||||
this._connection = connection;
|
||||
this._connectionIdentifier = basename + " Conn #" + number;
|
||||
this._open = true;
|
||||
|
||||
this._cachedStatements = new Map();
|
||||
|
@ -282,6 +285,11 @@ OpenedConnection.prototype = Object.freeze({
|
|||
this._clearIdleShrinkTimer();
|
||||
let deferred = Promise.defer();
|
||||
|
||||
AsyncShutdown.profileBeforeChange.addBlocker(
|
||||
"Sqlite.jsm: " + this._connectionIdentifier,
|
||||
deferred.promise
|
||||
);
|
||||
|
||||
// We need to take extra care with transactions during shutdown.
|
||||
//
|
||||
// If we don't have a transaction in progress, we can proceed with shutdown
|
||||
|
|
|
@ -396,6 +396,9 @@ var AddonManagerInternal = {
|
|||
providers: [],
|
||||
types: {},
|
||||
startupChanges: {},
|
||||
// Store telemetry details per addon provider
|
||||
telemetryDetails: {},
|
||||
|
||||
|
||||
// A read-only wrapper around the types dictionary
|
||||
typesProxy: Proxy.create({
|
||||
|
@ -458,6 +461,10 @@ var AddonManagerInternal = {
|
|||
|
||||
this.recordTimestamp("AMI_startup_begin");
|
||||
|
||||
// clear this for xpcshell test restarts
|
||||
for (let provider in this.telemetryDetails)
|
||||
delete this.telemetryDetails[provider];
|
||||
|
||||
let appChanged = undefined;
|
||||
|
||||
let oldAppVersion = null;
|
||||
|
@ -2192,12 +2199,20 @@ this.AddonManagerPrivate = {
|
|||
return this._simpleMeasures;
|
||||
},
|
||||
|
||||
getTelemetryDetails: function AMP_getTelemetryDetails() {
|
||||
return AddonManagerInternal.telemetryDetails;
|
||||
},
|
||||
|
||||
setTelemetryDetails: function AMP_setTelemetryDetails(aProvider, aDetails) {
|
||||
AddonManagerInternal.telemetryDetails[aProvider] = aDetails;
|
||||
},
|
||||
|
||||
// Start a timer, record a simple measure of the time interval when
|
||||
// timer.done() is called
|
||||
simpleTimer: function(aName) {
|
||||
let startTime = Date.now();
|
||||
return {
|
||||
done: () => AddonManagerPrivate.recordSimpleMeasure(aName, Date.now() - startTime)
|
||||
done: () => this.recordSimpleMeasure(aName, Date.now() - startTime)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1335,28 +1335,33 @@ function recursiveRemove(aFile) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the timestamp of the most recently modified file in a directory,
|
||||
* Returns the timestamp and leaf file name of the most recently modified
|
||||
* entry in a directory,
|
||||
* or simply the file's own timestamp if it is not a directory.
|
||||
*
|
||||
* @param aFile
|
||||
* A non-null nsIFile object
|
||||
* @return Epoch time, as described above. 0 for an empty directory.
|
||||
* @return [File Name, Epoch time], as described above.
|
||||
*/
|
||||
function recursiveLastModifiedTime(aFile) {
|
||||
try {
|
||||
let modTime = aFile.lastModifiedTime;
|
||||
let fileName = aFile.leafName;
|
||||
if (aFile.isFile())
|
||||
return aFile.lastModifiedTime;
|
||||
return [fileName, modTime];
|
||||
|
||||
if (aFile.isDirectory()) {
|
||||
let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
|
||||
let entry, time;
|
||||
let maxTime = aFile.lastModifiedTime;
|
||||
let entry;
|
||||
while ((entry = entries.nextFile)) {
|
||||
time = recursiveLastModifiedTime(entry);
|
||||
maxTime = Math.max(time, maxTime);
|
||||
let [subName, subTime] = recursiveLastModifiedTime(entry);
|
||||
if (subTime > modTime) {
|
||||
modTime = subTime;
|
||||
fileName = subName;
|
||||
}
|
||||
}
|
||||
entries.close();
|
||||
return maxTime;
|
||||
return [fileName, modTime];
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
|
@ -1364,7 +1369,7 @@ function recursiveLastModifiedTime(aFile) {
|
|||
}
|
||||
|
||||
// If the file is something else, just ignore it.
|
||||
return 0;
|
||||
return ["", 0];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1543,10 +1548,22 @@ var XPIProvider = {
|
|||
enabledAddons: null,
|
||||
// An array of add-on IDs of add-ons that were inactive during startup
|
||||
inactiveAddonIDs: [],
|
||||
// Count of unpacked add-ons
|
||||
unpackedAddons: 0,
|
||||
// Keep track of startup phases for telemetry
|
||||
runPhase: XPI_STARTING,
|
||||
// Keep track of the newest file in each add-on, in case we want to
|
||||
// report it to telemetry.
|
||||
_mostRecentlyModifiedFile: {},
|
||||
// Per-addon telemetry information
|
||||
_telemetryDetails: {},
|
||||
|
||||
/*
|
||||
* Set a value in the telemetry hash for a given ID
|
||||
*/
|
||||
setTelemetry: function XPI_setTelemetry(aId, aName, aValue) {
|
||||
if (!this._telemetryDetails[aId])
|
||||
this._telemetryDetails[aId] = {};
|
||||
this._telemetryDetails[aId][aName] = aValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds or updates a URI mapping for an Addon.id.
|
||||
|
@ -1689,6 +1706,11 @@ var XPIProvider = {
|
|||
this.installLocationsByName = {};
|
||||
// Hook for tests to detect when saving database at shutdown time fails
|
||||
this._shutdownError = null;
|
||||
// Clear this at startup for xpcshell test restarts
|
||||
this._telemetryDetails = {};
|
||||
// Register our details structure with AddonManager
|
||||
AddonManagerPrivate.setTelemetryDetails("XPI", this._telemetryDetails);
|
||||
|
||||
|
||||
AddonManagerPrivate.recordTimestamp("XPI_startup_begin");
|
||||
|
||||
|
@ -2033,18 +2055,22 @@ var XPIProvider = {
|
|||
let addonStates = {};
|
||||
aLocation.addonLocations.forEach(function(file) {
|
||||
let id = aLocation.getIDForLocation(file);
|
||||
let unpacked = 0;
|
||||
let [modFile, modTime] = recursiveLastModifiedTime(file);
|
||||
addonStates[id] = {
|
||||
descriptor: file.persistentDescriptor,
|
||||
mtime: recursiveLastModifiedTime(file)
|
||||
mtime: modTime
|
||||
};
|
||||
try {
|
||||
// get the install.rdf update time, if any
|
||||
file.append(FILE_INSTALL_MANIFEST);
|
||||
let rdfTime = file.lastModifiedTime;
|
||||
addonStates[id].rdfTime = rdfTime;
|
||||
this.unpackedAddons += 1;
|
||||
unpacked = 1;
|
||||
}
|
||||
catch (e) { }
|
||||
this._mostRecentlyModifiedFile[id] = modFile;
|
||||
this.setTelemetry(id, "unpacked", unpacked);
|
||||
}, this);
|
||||
|
||||
return addonStates;
|
||||
|
@ -2061,7 +2087,6 @@ var XPIProvider = {
|
|||
*/
|
||||
getInstallLocationStates: function XPI_getInstallLocationStates() {
|
||||
let states = [];
|
||||
this.unpackedAddons = 0;
|
||||
this.installLocations.forEach(function(aLocation) {
|
||||
let addons = aLocation.addonLocations;
|
||||
if (addons.length == 0)
|
||||
|
@ -3006,11 +3031,6 @@ var XPIProvider = {
|
|||
let changed = false;
|
||||
let knownLocations = XPIDatabase.getInstallLocations();
|
||||
|
||||
// Gather stats for addon telemetry
|
||||
let modifiedUnpacked = 0;
|
||||
let modifiedExManifest = 0;
|
||||
let modifiedXPI = 0;
|
||||
|
||||
// The install locations are iterated in reverse order of priority so when
|
||||
// there are multiple add-ons installed with the same ID the one that
|
||||
// should be visible is the first one encountered.
|
||||
|
@ -3045,15 +3065,22 @@ var XPIProvider = {
|
|||
if (aOldAddon.visible && !aOldAddon.active)
|
||||
XPIProvider.inactiveAddonIDs.push(aOldAddon.id);
|
||||
|
||||
// Check if the add-on is unpacked, and has had other files changed
|
||||
// on disk without the install.rdf manifest being changed
|
||||
if ((addonState.rdfTime) && (aOldAddon.updateDate != addonState.mtime)) {
|
||||
modifiedUnpacked += 1;
|
||||
if (aOldAddon.updateDate >= addonState.rdfTime)
|
||||
modifiedExManifest += 1;
|
||||
}
|
||||
else if (aOldAddon.updateDate != addonState.mtime) {
|
||||
modifiedXPI += 1;
|
||||
// Check if the add-on has been changed outside the XPI provider
|
||||
if (aOldAddon.updateDate != addonState.mtime) {
|
||||
// Is the add-on unpacked?
|
||||
if (addonState.rdfTime) {
|
||||
// Was the addon manifest "install.rdf" modified, or some other file?
|
||||
if (addonState.rdfTime > aOldAddon.updateDate) {
|
||||
this.setTelemetry(aOldAddon.id, "modifiedInstallRDF", 1);
|
||||
}
|
||||
else {
|
||||
this.setTelemetry(aOldAddon.id, "modifiedFile",
|
||||
this._mostRecentlyModifiedFile[aOldAddon.id]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.setTelemetry(aOldAddon.id, "modifiedXPI", 1);
|
||||
}
|
||||
}
|
||||
|
||||
// The add-on has changed if the modification time has changed, or
|
||||
|
@ -3107,12 +3134,6 @@ var XPIProvider = {
|
|||
}, this);
|
||||
}
|
||||
|
||||
// Tell Telemetry what we found
|
||||
AddonManagerPrivate.recordSimpleMeasure("modifiedUnpacked", modifiedUnpacked);
|
||||
if (modifiedUnpacked > 0)
|
||||
AddonManagerPrivate.recordSimpleMeasure("modifiedExceptInstallRDF", modifiedExManifest);
|
||||
AddonManagerPrivate.recordSimpleMeasure("modifiedXPI", modifiedXPI);
|
||||
|
||||
// Cache the new install location states
|
||||
let cache = JSON.stringify(this.getInstallLocationStates());
|
||||
Services.prefs.setCharPref(PREF_INSTALL_CACHE, cache);
|
||||
|
@ -3263,7 +3284,6 @@ var XPIProvider = {
|
|||
ERROR("Failed to process extension changes at startup", e);
|
||||
}
|
||||
}
|
||||
AddonManagerPrivate.recordSimpleMeasure("installedUnpacked", this.unpackedAddons);
|
||||
|
||||
if (aAppChanged) {
|
||||
// When upgrading the app and using a custom skin make sure it is still
|
||||
|
@ -3973,6 +3993,7 @@ var XPIProvider = {
|
|||
if (Services.appinfo.inSafeMode)
|
||||
return;
|
||||
|
||||
let timeStart = new Date();
|
||||
if (aMethod == "startup") {
|
||||
LOG("Registering manifest for " + aFile.path);
|
||||
Components.manager.addBootstrappedManifestLocation(aFile);
|
||||
|
@ -4019,6 +4040,7 @@ var XPIProvider = {
|
|||
LOG("Removing manifest for " + aFile.path);
|
||||
Components.manager.removeBootstrappedManifestLocation(aFile);
|
||||
}
|
||||
this.setTelemetry(aId, aMethod + "_MS", new Date() - timeStart);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -5331,7 +5353,8 @@ AddonInstall.prototype = {
|
|||
// Update the metadata in the database
|
||||
this.addon._sourceBundle = file;
|
||||
this.addon._installLocation = this.installLocation;
|
||||
this.addon.updateDate = recursiveLastModifiedTime(file); // XXX sync recursive scan
|
||||
let [mFile, mTime] = recursiveLastModifiedTime(file);
|
||||
this.addon.updateDate = mTime;
|
||||
this.addon.visible = true;
|
||||
if (isUpgrade) {
|
||||
this.addon = XPIDatabase.updateAddonMetadata(this.existingAddon, this.addon,
|
||||
|
|
|
@ -16,7 +16,8 @@ const IGNORE_PRIVATE = ["AddonAuthor", "AddonCompatibilityOverride",
|
|||
"registerProvider", "unregisterProvider",
|
||||
"addStartupChange", "removeStartupChange",
|
||||
"recordTimestamp", "recordSimpleMeasure",
|
||||
"getSimpleMeasures", "simpleTimer"];
|
||||
"getSimpleMeasures", "simpleTimer",
|
||||
"setTelemetryDetails", "getTelemetryDetails"];
|
||||
|
||||
function test_functions() {
|
||||
for (let prop in AddonManager) {
|
||||
|
|
|
@ -3851,6 +3851,7 @@ XREMain::XRE_mainRun()
|
|||
if (obsService)
|
||||
obsService->NotifyObservers(nullptr, "final-ui-startup", nullptr);
|
||||
|
||||
(void)appStartup->DoneStartingUp();
|
||||
appStartup->GetShuttingDown(&mShuttingDown);
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче