This commit is contained in:
Ryan VanderMeulen 2013-10-11 15:41:41 -04:00
Родитель abbd67c311 0d558f650a
Коммит b2624cd00e
42 изменённых файлов: 720 добавлений и 210 удалений

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

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