Merge fx-team to mozilla-central

This commit is contained in:
Wes Kocher 2014-02-12 16:26:16 -08:00
Родитель 74a3c05243 4e49d56880
Коммит f9cfa703c8
72 изменённых файлов: 3685 добавлений и 2043 удалений

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

@ -30,6 +30,13 @@
<menupopup id="customization-toolbar-menu" onpopupshowing="onViewToolbarsPopupShowing(event)"/>
</button>
<spacer flex="1"/>
<label id="customization-undo-reset"
hidden="true"
onclick="gCustomizeMode.undoReset();"
onkeypress="gCustomizeMode.undoReset();"
class="text-link">
&undoCmd.label;
</label>
<button id="customization-reset-button" oncommand="gCustomizeMode.reset();" label="&customizeMode.restoreDefaults;" class="customizationmode-button"/>
</hbox>
</vbox>

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

@ -289,6 +289,12 @@
break;
case "popupshowing":
this.setAttribute("panelopen", "true");
// Bug 941196 - The panel can get taller when opening a subview. Disabling
// autoPositioning means that the panel won't jump around if an opened
// subview causes the panel to exceed the dimensions of the screen in the
// direction that the panel originally opened in. This property resets
// every time the popup closes, which is why we have to set it each time.
this._panel.autoPosition = false;
this._syncContainerWithMainView();
break;
case "popupshown":

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

@ -128,6 +128,8 @@ let gGroupWrapperCache = new Map();
let gSingleWrapperCache = new WeakMap();
let gListeners = new Set();
let gUIStateBeforeReset = null;
let gModuleName = "[CustomizableUI]";
#include logging.js
@ -693,6 +695,10 @@ let CustomizableUIInternal = {
onWidgetAdded: function(aWidgetId, aArea, aPosition) {
this.insertNode(aWidgetId, aArea, aPosition, true);
if (!gResetting) {
gUIStateBeforeReset = null;
}
},
onWidgetRemoved: function(aWidgetId, aArea) {
@ -749,10 +755,20 @@ let CustomizableUIInternal = {
windowCache.delete(aWidgetId);
}
}
if (!gResetting) {
gUIStateBeforeReset = null;
}
},
onWidgetMoved: function(aWidgetId, aArea, aOldPosition, aNewPosition) {
this.insertNode(aWidgetId, aArea, aNewPosition);
if (!gResetting) {
gUIStateBeforeReset = null;
}
},
onCustomizeEnd: function(aWindow) {
gUIStateBeforeReset = null;
},
registerBuildArea: function(aArea, aNode) {
@ -2049,6 +2065,20 @@ let CustomizableUIInternal = {
reset: function() {
gResetting = true;
this._resetUIState();
// Rebuild each registered area (across windows) to reflect the state that
// was reset above.
this._rebuildRegisteredAreas();
gResetting = false;
},
_resetUIState: function() {
try {
gUIStateBeforeReset = Services.prefs.getCharPref(kPrefCustomizationState);
} catch(e) { }
Services.prefs.clearUserPref(kPrefCustomizationState);
LOG("State reset");
@ -2062,9 +2092,9 @@ let CustomizableUIInternal = {
for (let [areaId,] of gAreas) {
this.restoreStateForArea(areaId);
}
},
// Rebuild each registered area (across windows) to reflect the state that
// was reset above.
_rebuildRegisteredAreas: function() {
for (let [areaId, areaNodes] of gBuildAreas) {
let placements = gPlacements.get(areaId);
for (let areaNode of areaNodes) {
@ -2078,7 +2108,23 @@ let CustomizableUIInternal = {
}
}
}
gResetting = false;
},
/**
* Undoes a previous reset, restoring the state of the UI to the state prior to the reset.
*/
undoReset: function() {
if (!gUIStateBeforeReset) {
return;
}
Services.prefs.setCharPref(kPrefCustomizationState, gUIStateBeforeReset);
this.loadSavedState();
for (let areaId of Object.keys(gSavedState.placements)) {
let placements = gSavedState.placements[areaId];
gPlacements.set(areaId, placements);
}
this._rebuildRegisteredAreas();
gUIStateBeforeReset = null;
},
/**
@ -2832,6 +2878,25 @@ this.CustomizableUI = {
reset: function() {
CustomizableUIInternal.reset();
},
/**
* Undo the previous reset, can only be called immediately after a reset.
* @return a promise that will be resolved when the operation is complete.
*/
undoReset: function() {
CustomizableUIInternal.undoReset();
},
/**
* Can the last Restore Defaults operation be undone.
*
* @return A boolean stating whether an undo of the
* Restore Defaults can be performed.
*/
get canUndoReset() {
return !!gUIStateBeforeReset;
},
/**
* Get the placement of a widget. This is by far the best way to obtain
* information about what the state of your widget is. The internals of

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

@ -239,6 +239,7 @@ CustomizeMode.prototype = {
document.getElementById("PanelUI-quit").setAttribute("disabled", true);
this._updateResetButton();
this._updateUndoResetButton();
this._skipSourceNodeCheck = Services.prefs.getPrefType(kSkipSourceNodePref) == Ci.nsIPrefBranch.PREF_BOOL &&
Services.prefs.getBoolPref(kSkipSourceNodePref);
@ -854,12 +855,35 @@ CustomizeMode.prototype = {
this.persistCurrentSets(true);
this._updateResetButton();
this._updateUndoResetButton();
this._updateEmptyPaletteNotice();
this._showPanelCustomizationPlaceholders();
this.resetting = false;
}.bind(this)).then(null, ERROR);
},
undoReset: function() {
this.resetting = true;
return Task.spawn(function() {
this._removePanelCustomizationPlaceholders();
yield this.depopulatePalette();
yield this._unwrapToolbarItems();
CustomizableUI.undoReset();
yield this._wrapToolbarItems();
yield this.populatePalette();
this.persistCurrentSets(true);
this._updateResetButton();
this._updateUndoResetButton();
this._updateEmptyPaletteNotice();
this.resetting = false;
}.bind(this)).then(null, ERROR);
},
_onToolbarVisibilityChange: function(aEvent) {
let toolbar = aEvent.target;
if (aEvent.detail.visible && toolbar.getAttribute("customizable") == "true") {
@ -958,6 +982,7 @@ CustomizeMode.prototype = {
this._changed = true;
if (!this.resetting) {
this._updateResetButton();
this._updateUndoResetButton();
this._updateEmptyPaletteNotice();
}
this.dispatchToolboxEvent("customizationchange");
@ -973,6 +998,11 @@ CustomizeMode.prototype = {
btn.disabled = CustomizableUI.inDefaultState;
},
_updateUndoResetButton: function() {
let undoReset = this.document.getElementById("customization-undo-reset");
undoReset.hidden = !CustomizableUI.canUndoReset;
},
handleEvent: function(aEvent) {
switch(aEvent.type) {
case "toolbarvisibilitychange":

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

@ -65,4 +65,5 @@ skip-if = os == "linux"
[browser_956602_remove_special_widget.js]
[browser_969427_recreate_destroyed_widget_after_reset.js]
[browser_969661_character_encoding_navbar_disabled.js]
[browser_970511_undo_restore_default.js]
[browser_panel_toggle.js]

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

@ -0,0 +1,65 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Restoring default should show an "undo" option which undoes the restoring operation.
add_task(function() {
let homeButtonId = "home-button";
CustomizableUI.removeWidgetFromArea(homeButtonId);
yield startCustomizing();
ok(!CustomizableUI.inDefaultState, "Not in default state to begin with");
is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
let undoReset = document.getElementById("customization-undo-reset");
is(undoReset.hidden, true, "The undo button is hidden before reset");
yield gCustomizeMode.reset();
ok(CustomizableUI.inDefaultState, "In default state after reset");
is(undoReset.hidden, false, "The undo button is visible after reset");
undoReset.click();
yield waitForCondition(function() !gCustomizeMode.resetting);
ok(!CustomizableUI.inDefaultState, "Not in default state after reset-undo");
is(undoReset.hidden, true, "The undo button is hidden after clicking on the undo button");
is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
yield gCustomizeMode.reset();
});
// Performing an action after a reset will hide the reset button.
add_task(function() {
let homeButtonId = "home-button";
CustomizableUI.removeWidgetFromArea(homeButtonId);
ok(!CustomizableUI.inDefaultState, "Not in default state to begin with");
is(CustomizableUI.getPlacementOfWidget(homeButtonId), null, "Home button is in palette");
let undoReset = document.getElementById("customization-undo-reset");
is(undoReset.hidden, true, "The undo button is hidden before reset");
yield gCustomizeMode.reset();
ok(CustomizableUI.inDefaultState, "In default state after reset");
is(undoReset.hidden, false, "The undo button is visible after reset");
CustomizableUI.addWidgetToArea(homeButtonId, CustomizableUI.AREA_PANEL);
is(undoReset.hidden, true, "The undo button is hidden after another change");
});
// "Restore defaults", exiting customize, and re-entering shouldn't show the Undo button
add_task(function() {
let undoReset = document.getElementById("customization-undo-reset");
is(undoReset.hidden, true, "The undo button is hidden before a reset");
ok(!CustomizableUI.inDefaultState, "The browser should not be in default state");
yield gCustomizeMode.reset();
is(undoReset.hidden, false, "The undo button is hidden after a reset");
yield endCustomizing();
yield startCustomizing();
is(undoReset.hidden, true, "The undo reset button should be hidden after entering customization mode");
});
add_task(function asyncCleanup() {
yield gCustomizeMode.reset();
yield endCustomizing();
});

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

@ -13,8 +13,10 @@ support-files =
# Bug 916763 - too many intermittent failures
skip-if = true
[browser_inspector_markup_edit.js]
# Bug 904953 - too many intermittent failures on Linux
skip-if = os == "linux"
[browser_inspector_markup_edit_2.js]
[browser_inspector_markup_edit_3.js]
[browser_inspector_markup_edit_4.js]
[browser_inspector_markup_add_attributes.js]
[browser_inspector_markup_edit_outerhtml.js]
[browser_inspector_markup_edit_outerhtml2.js]
[browser_inspector_markup_mutation.js]

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

@ -0,0 +1,170 @@
/* Any copyright", " is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests that adding various types of attributes to nodes in the markup-view
* works as expected. Also checks that the changes are properly undoable and
* redoable. For each step in the test, we:
* - Create a new DIV
* - Make the change, check that the change was made as we expect
* - Undo the change, check that the node is back in its original state
* - Redo the change, check that the node change was made again correctly.
*/
waitForExplicitFinish();
let TEST_URL = "data:text/html,<div>markup-view attributes addition test</div>";
let TEST_DATA = [{
desc: "Add an attribute value without closing \"",
enteredText: 'style="display: block;',
expectedAttributes: {
style: "display: block;"
}
}, {
desc: "Add an attribute value without closing '",
enteredText: "style='display: inline;",
expectedAttributes: {
style: "display: inline;"
}
}, {
desc: "Add an attribute wrapped with with double quotes double quote in it",
enteredText: 'style="display: "inline',
expectedAttributes: {
style: "display: ",
inline: ""
}
}, {
desc: "Add an attribute wrapped with single quotes with single quote in it",
enteredText: "style='display: 'inline",
expectedAttributes: {
style: "display: ",
inline: ""
}
}, {
desc: "Add an attribute with no value",
enteredText: "disabled",
expectedAttributes: {
disabled: ""
}
}, {
desc: "Add multiple attributes with no value",
enteredText: "disabled autofocus",
expectedAttributes: {
disabled: "",
autofocus: ""
}
}, {
desc: "Add multiple attributes with no value, and some with value",
enteredText: "disabled name='name' data-test='test' autofocus",
expectedAttributes: {
disabled: "",
autofocus: "",
name: "name",
'data-test': "test"
}
}, {
desc: "Add attribute with xmlns",
enteredText: "xmlns:edi='http://ecommerce.example.org/schema'",
expectedAttributes: {
'xmlns:edi': "http://ecommerce.example.org/schema"
}
}, {
desc: "Mixed single and double quotes",
enteredText: "name=\"hi\" maxlength='not a number'",
expectedAttributes: {
maxlength: "not a number",
name: "hi"
}
}, {
desc: "Invalid attribute name",
enteredText: "x='y' <why-would-you-do-this>=\"???\"",
expectedAttributes: {
x: "y"
}
}, {
desc: "Double quote wrapped in single quotes",
enteredText: "x='h\"i'",
expectedAttributes: {
x: "h\"i"
}
}, {
desc: "Single quote wrapped in double quotes",
enteredText: "x=\"h'i\"",
expectedAttributes: {
x: "h'i"
}
}, {
desc: "No quote wrapping",
enteredText: "a=b x=y data-test=Some spaced data",
expectedAttributes: {
a: "b",
x: "y",
"data-test": "Some",
spaced: "",
data: ""
}
}, {
desc: "Duplicate Attributes",
enteredText: "a=b a='c' a=\"d\"",
expectedAttributes: {
a: "b"
}
}, {
desc: "Inline styles",
enteredText: "style=\"font-family: 'Lucida Grande', sans-serif; font-size: 75%;\"",
expectedAttributes: {
style: "font-family: 'Lucida Grande', sans-serif; font-size: 75%;"
}
}, {
desc: "Object attribute names",
enteredText: "toString=\"true\" hasOwnProperty=\"false\"",
expectedAttributes: {
toString: "true",
hasOwnProperty: "false"
}
}, {
desc: "Add event handlers",
enteredText: "onclick=\"javascript: throw new Error('wont fire');\" onload=\"alert('here');\"",
expectedAttributes: {
onclick: "javascript: throw new Error('wont fire');",
onload: "alert('here');"
}
}];
function test() {
Task.spawn(function() {
info("Opening the inspector on the test URL");
let args = yield addTab(TEST_URL).then(openInspector);
let inspector = args.inspector;
let markup = inspector.markup;
info("Selecting the test node");
let div = getNode("div");
yield selectNode(div, inspector);
let editor = getContainerForRawNode(markup, div).editor;
for (let test of TEST_DATA) {
info("Starting test: " + test.desc);
info("Enter the new attribute(s) test: " + test.enteredText);
let nodeMutated = inspector.once("markupmutation");
setEditableFieldValue(editor.newAttr, test.enteredText, inspector);
yield nodeMutated;
info("Assert that the attribute(s) has/have been applied correctly");
assertAttributes(div, test.expectedAttributes);
info("Undo the change");
yield undoChange(inspector);
info("Assert that the attribute(s) has/have been removed correctly");
assertAttributes(div, {});
}
yield inspector.once("inspector-updated");
gBrowser.removeCurrentTab();
}).then(null, ok.bind(null, false)).then(finish);
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -0,0 +1,51 @@
/* Any copyright", " is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that an existing attribute can be modified
waitForExplicitFinish();
const TEST_URL = "data:text/html,<div id='test-div'>Test modifying my ID attribute</div>";
function test() {
Task.spawn(function() {
info("Opening the inspector on the test page");
let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
info("Selecting the test node");
let node = content.document.getElementById("test-div");
yield selectNode(node, inspector);
info("Verify attributes, only ID should be there for now");
assertAttributes(node, {
id: "test-div"
});
info("Focus the ID attribute and change its content");
let editor = getContainerForRawNode(inspector.markup, node).editor;
let attr = editor.attrs["id"].querySelector(".editable");
let mutated = inspector.once("markupmutation");
setEditableFieldValue(attr,
attr.textContent + ' class="newclass" style="color:green"', inspector);
yield mutated;
info("Verify attributes, should have ID, class and style");
assertAttributes(node, {
id: "test-div",
class: "newclass",
style: "color:green"
});
info("Trying to undo the change");
yield undoChange(inspector);
assertAttributes(node, {
id: "test-div"
});
yield inspector.once("inspector-updated");
gBrowser.removeCurrentTab();
}).then(null, ok.bind(null, false)).then(finish);
}

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

@ -0,0 +1,45 @@
/* Any copyright", " is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that a node's tagname can be edited in the markup-view
waitForExplicitFinish();
const TEST_URL = "data:text/html,<div id='retag-me'><div id='retag-me-2'></div></div>";
function test() {
Task.spawn(function() {
info("Opening the inspector on the test page");
let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
yield inspector.markup.expandAll();
info("Selecting the test node");
let node = content.document.getElementById("retag-me");
let child = content.document.querySelector("#retag-me-2");
yield selectNode(node, inspector);
let container = getContainerForRawNode(inspector.markup, node);
is(node.tagName, "DIV", "We've got #retag-me element, it's a DIV");
ok(container.expanded, "It is expanded");
is(child.parentNode, node, "Child #retag-me-2 is inside #retag-me");
info("Changing the tagname");
let mutated = inspector.once("markupmutation");
let tagEditor = container.editor.tag;
setEditableFieldValue(tagEditor, "p", inspector);
yield mutated;
info("Checking that the tagname change was done");
let node = content.document.getElementById("retag-me");
let container = getContainerForRawNode(inspector.markup, node);
is(node.tagName, "P", "We've got #retag-me, it should now be a P");
ok(container.expanded, "It is still expanded");
ok(container.selected, "It is still selected");
is(child.parentNode, node, "Child #retag-me-2 is still inside #retag-me");
gBrowser.removeCurrentTab();
}).then(null, ok.bind(null, false)).then(finish);
}

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

@ -0,0 +1,36 @@
/* Any copyright", " is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that a node can be deleted from the markup-view with the delete key
waitForExplicitFinish();
const TEST_URL = "data:text/html,<div id='delete-me'></div>";
function test() {
Task.spawn(function() {
info("Opening the inspector on the test page");
let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
info("Selecting the test node by clicking on it to make sure it receives focus");
let node = content.document.getElementById("delete-me");
yield clickContainer(node, inspector);
info("Deleting the element with the keyboard");
let mutated = inspector.once("markupmutation");
EventUtils.sendKey("delete", inspector.panelWin);
yield mutated;
info("Checking that it's gone, baby gone!");
ok(!content.document.getElementById("delete-me"), "The test node does not exist");
yield undoChange(inspector);
ok(content.document.getElementById("delete-me"), "The test node is back!");
yield inspector.once("inspector-updated");
gBrowser.removeCurrentTab();
}).then(null, ok.bind(null, false)).then(finish);
}

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

@ -8,6 +8,7 @@ let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let TargetFactory = devtools.TargetFactory;
let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
let promise = devtools.require("sdk/core/promise");
let {getInplaceEditorForSpan: inplaceEditor} = devtools.require("devtools/shared/inplace-editor");
// Clear preferences that may be set during the course of tests.
function clearUserPrefs() {
@ -18,15 +19,23 @@ function clearUserPrefs() {
registerCleanupFunction(clearUserPrefs);
Services.prefs.setBoolPref("devtools.debugger.log", true);
SimpleTest.registerCleanupFunction(() => {
Services.prefs.clearUserPref("devtools.debugger.log");
});
/**
* Add a new test tab in the browser and load the given url.
* @param {String} url The url to be loaded in the new tab
* @return a promise that resolves when the url is loaded
*/
function addTab(url) {
let def = promise.defer();
function getContainerForRawNode(markupView, rawNode) {
let front = markupView.walker.frontForRawNode(rawNode);
let container = markupView.getContainer(front);
return container;
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
info("URL " + url + " loading complete into new test tab");
waitForFocus(def.resolve, content);
}, true);
content.location = url;
return def.promise;
}
/**
@ -34,19 +43,41 @@ function getContainerForRawNode(markupView, rawNode) {
* @return a promise that resolves when the inspector is ready
*/
function openInspector() {
let deferred = promise.defer();
let def = promise.defer();
let target = TargetFactory.forTab(gBrowser.selectedTab);
gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
info("Toolbox open");
let inspector = toolbox.getCurrentPanel();
inspector.once("inspector-updated", () => {
deferred.resolve({toolbox: toolbox, inspector: inspector});
info("Inspector panel active and ready");
def.resolve({toolbox: toolbox, inspector: inspector});
});
}).then(null, console.error);
return deferred.promise;
return def.promise;
}
/**
* Get the MarkupContainer object instance that corresponds to the given
* HTML node
* @param {MarkupView} markupView The instance of MarkupView currently loaded into the inspector panel
* @param {DOMNode} rawNode The DOM node for which the container is required
* @return {MarkupContainer}
*/
function getContainerForRawNode(markupView, rawNode) {
let front = markupView.walker.frontForRawNode(rawNode);
let container = markupView.getContainer(front);
ok(container, "A markup-container object was found");
return container;
}
/**
* Simple DOM node accesor function that takes either a node or a string css
* selector as argument and returns the corresponding node
* @param {String|DOMNode} nodeOrSelector
* @return {DOMNode}
*/
function getNode(nodeOrSelector) {
let node = nodeOrSelector;
@ -61,23 +92,30 @@ function getNode(nodeOrSelector) {
/**
* Set the inspector's current selection to a node or to the first match of the
* given css selector
* @param {String|DOMNode} nodeOrSelector
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
* @param {String} reason Defaults to "test" which instructs the inspector not to highlight the node upon selection
* @return a promise that resolves when the inspector is updated with the new
* node
*/
function selectNode(nodeOrSelector, inspector) {
function selectNode(nodeOrSelector, inspector, reason="test") {
info("Selecting the node " + nodeOrSelector);
let node = getNode(nodeOrSelector);
let updated = inspector.once("inspector-updated");
inspector.selection.setNode(node, "test");
inspector.selection.setNode(node, reason);
return updated;
}
/**
* Simulate a mouse-over on the markup-container (a line in the markup-view)
* that corresponds to the node or selector passed.
* @param {String|DOMNode} nodeOrSelector
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
* @return a promise that resolves when the container is hovered and the higlighter
* is shown on the corresponding node
*/
function hoverContainer(nodeOrSelector, inspector) {
info("Hovering over the markup-container for node " + nodeOrSelector);
let highlit = inspector.toolbox.once("node-highlight");
let container = getContainerForRawNode(inspector.markup, getNode(nodeOrSelector));
EventUtils.synthesizeMouse(container.tagLine, 2, 2, {type: "mousemove"},
@ -88,9 +126,12 @@ function hoverContainer(nodeOrSelector, inspector) {
/**
* Simulate a click on the markup-container (a line in the markup-view)
* that corresponds to the node or selector passed.
* @param {String|DOMNode} nodeOrSelector
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
* @return a promise that resolves when the node has been selected.
*/
function clickContainer(nodeOrSelector, inspector) {
info("Clicking on the markup-container for node " + nodeOrSelector);
let updated = inspector.once("inspector-updated");
let container = getContainerForRawNode(inspector.markup, getNode(nodeOrSelector));
EventUtils.synthesizeMouseAtCenter(container.tagLine, {type: "mousedown"},
@ -102,6 +143,7 @@ function clickContainer(nodeOrSelector, inspector) {
/**
* Checks if the highlighter is visible currently
* @return {Boolean}
*/
function isHighlighterVisible() {
let outline = gBrowser.selectedBrowser.parentNode.querySelector(".highlighter-container .highlighter-outline");
@ -110,18 +152,91 @@ function isHighlighterVisible() {
/**
* Simulate the mouse leaving the markup-view area
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
* @return a promise when done
*/
function mouseLeaveMarkupView(inspector) {
let deferred = promise.defer();
info("Leaving the markup-view area");
let def = promise.defer();
// Find another element to mouseover over in order to leave the markup-view
let btn = inspector.toolbox.doc.querySelector(".toolbox-dock-button");
EventUtils.synthesizeMouse(btn, 2, 2, {type: "mousemove"},
inspector.toolbox.doc.defaultView);
executeSoon(deferred.resolve);
executeSoon(def.resolve);
return deferred.promise;
return def.promise;
}
/**
* Focus a given editable element, enter edit mode, set value, and commit
* @param {DOMNode} field The element that gets editable after receiving focus and <ENTER> keypress
* @param {String} value The string value to be set into the edited field
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
*/
function setEditableFieldValue(field, value, inspector) {
field.focus();
EventUtils.sendKey("return", inspector.panelWin);
let input = inplaceEditor(field).input;
ok(input, "Found editable field for setting value: " + value);
input.value = value;
EventUtils.sendKey("return", inspector.panelWin);
}
/**
* Checks that a node has the given attributes
*
* @param {HTMLNode} element The node to check.
* @param {Object} attrs An object containing the attributes to check.
* e.g. {id: "id1", class: "someclass"}
*
* Note that node.getAttribute() returns attribute values provided by the HTML
* parser. The parser only provides unescaped entities so &amp; will return &.
*/
function assertAttributes(element, attrs) {
is(element.attributes.length, Object.keys(attrs).length,
"Node has the correct number of attributes.");
for (let attr in attrs) {
is(element.getAttribute(attr), attrs[attr],
"Node has the correct " + attr + " attribute.");
}
}
/**
* Undo the last markup-view action and wait for the corresponding mutation to
* occur
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
* @return a promise that resolves when the markup-mutation has been treated or
* rejects if no undo action is possible
*/
function undoChange(inspector) {
let canUndo = inspector.markup.undo.canUndo();
ok(canUndo, "The last change in the markup-view can be undone");
if (!canUndo) {
return promise.reject();
}
let mutated = inspector.once("markupmutation");
inspector.markup.undo.undo();
return mutated;
}
/**
* Redo the last markup-view action and wait for the corresponding mutation to
* occur
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
* @return a promise that resolves when the markup-mutation has been treated or
* rejects if no redo action is possible
*/
function redoChange(inspector) {
let canRedo = inspector.markup.undo.canRedo();
ok(canRedo, "The last change in the markup-view can be redone");
if (!canRedo) {
return promise.reject();
}
let mutated = inspector.once("markupmutation");
inspector.markup.undo.redo();
return mutated;
}

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

@ -0,0 +1,153 @@
/* -*- Mode: javascript; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const cssTokenizer = require("devtools/sourceeditor/css-tokenizer");
/**
* Returns the string enclosed in quotes
*/
function quoteString(string) {
let hasDoubleQuotes = string.contains('"');
let hasSingleQuotes = string.contains("'");
if (hasDoubleQuotes && !hasSingleQuotes) {
// In this case, no escaping required, just enclose in single-quotes
return "'" + string + "'";
}
// In all other cases, enclose in double-quotes, and escape any double-quote
// that may be in the string
return '"' + string.replace(/"/g, '\"') + '"';
}
/**
* Returns an array of CSS declarations given an string.
* For example, parseDeclarations("width: 1px; height: 1px") would return
* [{name:"width", value: "1px"}, {name: "height", "value": "1px"}]
*
* The input string is assumed to only contain declarations so { and } characters
* will be treated as part of either the property or value, depending where it's
* found.
*
* @param {string} inputString
* An input string of CSS
* @return {Array} an array of objects with the following signature:
* [{"name": string, "value": string, "priority": string}, ...]
*/
function parseDeclarations(inputString) {
let tokens = cssTokenizer(inputString);
let declarations = [{name: "", value: "", priority: ""}];
let current = "", hasBang = false, lastProp;
for (let token of tokens) {
lastProp = declarations[declarations.length - 1];
if (token.tokenType === ":") {
if (!lastProp.name) {
// Set the current declaration name if there's no name yet
lastProp.name = current.trim();
current = "";
hasBang = false;
} else {
// Otherwise, just append ':' to the current value (declaration value
// with colons)
current += ":";
}
} else if (token.tokenType === ";") {
lastProp.value = current.trim();
current = "";
hasBang = false;
declarations.push({name: "", value: "", priority: ""});
} else {
switch(token.tokenType) {
case "IDENT":
if (token.value === "important" && hasBang) {
lastProp.priority = "important";
hasBang = false;
} else {
if (hasBang) {
current += "!";
}
current += token.value;
}
break;
case "WHITESPACE":
current += " ";
break;
case "DIMENSION":
current += token.repr;
break;
case "HASH":
current += "#" + token.value;
break;
case "URL":
current += "url(" + quoteString(token.value) + ")";
break;
case "FUNCTION":
current += token.value + "(";
break;
case ")":
current += token.tokenType;
break;
case "EOF":
break;
case "DELIM":
if (token.value === "!") {
hasBang = true;
} else {
current += token.value;
}
break;
case "STRING":
current += quoteString(token.value);
break;
case "{":
case "}":
current += token.tokenType;
break;
default:
current += token.value;
break;
}
}
}
// Handle whatever trailing properties or values might still be there
if (current) {
if (!lastProp.name) {
// Trailing property found, e.g. p1:v1;p2:v2;p3
lastProp.name = current.trim();
} else {
// Trailing value found, i.e. value without an ending ;
lastProp.value += current.trim();
}
}
// Remove declarations that have neither a name nor a value
declarations = declarations.filter(prop => prop.name || prop.value);
return declarations;
};
exports.parseDeclarations = parseDeclarations;
/**
* Expects a single CSS value to be passed as the input and parses the value
* and priority.
*
* @param {string} value The value from the text editor.
* @return {object} an object with 'value' and 'priority' properties.
*/
function parseSingleValue(value) {
let declaration = parseDeclarations("a: " + value + ";")[0];
return {
value: declaration ? declaration.value : "",
priority: declaration ? declaration.priority : ""
};
};
exports.parseSingleValue = parseSingleValue;

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

@ -14,7 +14,8 @@ const {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles"
const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
const {Tooltip, SwatchColorPickerTooltip} = require("devtools/shared/widgets/Tooltip");
const {OutputParser} = require("devtools/output-parser");
const { PrefObserver, PREF_ORIG_SOURCES } = require("devtools/styleeditor/utils");
const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/styleeditor/utils");
const {parseSingleValue, parseDeclarations} = require("devtools/styleinspector/css-parsing-utils");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -582,7 +583,7 @@ Rule.prototype = {
let promise = aModifications.apply().then(() => {
let cssProps = {};
for (let cssProp of parseCSSText(this.style.cssText)) {
for (let cssProp of parseDeclarations(this.style.cssText)) {
cssProps[cssProp.name] = cssProp;
}
@ -692,7 +693,7 @@ Rule.prototype = {
_getTextProperties: function() {
let textProps = [];
let store = this.elementStyle.store;
let props = parseCSSText(this.style.cssText);
let props = parseDeclarations(this.style.cssText);
for (let prop of props) {
let name = prop.name;
if (this.inherited && !domUtils.isInheritedProperty(name)) {
@ -1850,17 +1851,13 @@ RuleEditor.prototype = {
return;
}
// Deal with adding declarations later (once editor has been destroyed).
// If aValue is just a name, will make a new property with empty value.
this.multipleAddedProperties = parseCSSText(aValue);
if (!this.multipleAddedProperties.length) {
this.multipleAddedProperties = [{
name: aValue,
value: "",
priority: ""
}];
}
// parseDeclarations allows for name-less declarations, but in the present
// case, we're creating a new declaration, it doesn't make sense to accept
// these entries
this.multipleAddedProperties = parseDeclarations(aValue).filter(d => d.name);
// Blur the editor field now and deal with adding declarations later when
// the field gets destroyed (see _newPropertyDestroy)
this.editor.input.blur();
},
@ -2263,17 +2260,16 @@ TextPropertyEditor.prototype = {
if (aValue.trim() === "") {
this.remove();
} else {
// Adding multiple rules inside of name field overwrites the current
// property with the first, then adds any more onto the property list.
let properties = parseCSSText(aValue);
if (properties.length > 0) {
this.prop.setName(properties[0].name);
this.prop.setValue(properties[0].value, properties[0].priority);
let properties = parseDeclarations(aValue);
this.ruleEditor.addProperties(properties.slice(1), this.prop);
} else {
this.prop.setName(aValue);
if (properties.length) {
this.prop.setName(properties[0].name);
if (properties.length > 1) {
this.prop.setValue(properties[0].value, properties[0].priority);
this.ruleEditor.addProperties(properties.slice(1), this.prop);
}
}
}
}
@ -2320,7 +2316,7 @@ TextPropertyEditor.prototype = {
let {propertiesToAdd,firstValue} = this._getValueAndExtraProperties(aValue);
// First, set this property value (common case, only modified a property)
let val = parseCSSValue(firstValue);
let val = parseSingleValue(firstValue);
this.prop.setValue(val.value, val.priority);
this.removeOnRevert = false;
this.committed.value = this.prop.value;
@ -2356,36 +2352,31 @@ TextPropertyEditor.prototype = {
* firstValue: A string containing a simple value, like
* "red" or "100px!important"
* propertiesToAdd: An array with additional properties, following the
* parseCSSText format of {name,value,priority}
* parseDeclarations format of {name,value,priority}
*/
_getValueAndExtraProperties: function(aValue) {
// The inplace editor will prevent manual typing of multiple properties,
// but we need to deal with the case during a paste event.
// Adding multiple properties inside of value editor sets value with the
// first, then adds any more onto the property list (below this property).
let properties = parseCSSText(aValue);
let propertiesToAdd = [];
let firstValue = aValue;
let propertiesToAdd = [];
if (properties.length > 0) {
// If text like "red; width: 1px;" was entered in, handle this as two
// separate properties (setting value here to red and adding a new prop).
let propertiesNoName = parseCSSText("a:" + aValue);
let enteredValueFirst = propertiesNoName.length > properties.length;
let properties = parseDeclarations(aValue);
let firstProp = properties[0];
propertiesToAdd = properties.slice(1);
if (enteredValueFirst) {
firstProp = propertiesNoName[0];
propertiesToAdd = propertiesNoName.slice(1);
// Check to see if the input string can be parsed as multiple properties
if (properties.length) {
// Get the first property value (if any), and any remaining properties (if any)
if (!properties[0].name && properties[0].value) {
firstValue = properties[0].value;
propertiesToAdd = properties.slice(1);
}
// In some cases, the value could be a property:value pair itself.
// Join them as one value string and append potentially following properties
else if (properties[0].name && properties[0].value) {
firstValue = properties[0].name + ": " + properties[0].value;
propertiesToAdd = properties.slice(1);
}
// If "red; width: 1px", then set value to "red"
// If "color: red; width: 1px;", then set value to "color: red;"
firstValue = enteredValueFirst ?
firstProp.value + "!" + firstProp.priority :
firstProp.name + ": " + firstProp.value + "!" + firstProp.priority;
}
return {
@ -2395,7 +2386,7 @@ TextPropertyEditor.prototype = {
},
_applyNewValue: function(aValue) {
let val = parseCSSValue(aValue);
let val = parseSingleValue(aValue);
// Any property should be removed if has an empty value.
if (val.value.trim() === "") {
this.remove();
@ -2419,7 +2410,7 @@ TextPropertyEditor.prototype = {
return;
}
let val = parseCSSValue(aValue);
let val = parseSingleValue(aValue);
// Live previewing the change without committing just yet, that'll be done in _onValueDone
// If it was not a valid value, apply an empty string to reset the live preview
@ -2439,7 +2430,7 @@ TextPropertyEditor.prototype = {
isValid: function(aValue) {
let name = this.prop.name;
let value = typeof aValue == "undefined" ? this.prop.value : aValue;
let val = parseCSSValue(value);
let val = parseSingleValue(value);
let style = this.doc.createElementNS(HTML_NS, "div").style;
let prefs = Services.prefs;
@ -2605,64 +2596,14 @@ function throttle(func, wait, scope) {
};
}
/**
* Pull priority (!important) out of the value provided by a
* value editor.
*
* @param {string} aValue
* The value from the text editor.
* @return {object} an object with 'value' and 'priority' properties.
*/
function parseCSSValue(aValue) {
let pieces = aValue.split("!", 2);
return {
value: pieces[0].trim(),
priority: (pieces.length > 1 ? pieces[1].trim() : "")
};
}
/**
* Return an array of CSS properties given an input string
* For example, parseCSSText("width: 1px; height: 1px") would return
* [{name:"width", value: "1px"}, {name: "height", "value": "1px"}]
*
* @param {string} aCssText
* An input string of CSS
* @return {Array} an array of objects with the following signature:
* [{"name": string, "value": string, "priority": string}, ...]
*/
function parseCSSText(aCssText) {
let lines = aCssText.match(CSS_LINE_RE);
let props = [];
[].forEach.call(lines, (line, i) => {
let [, name, value, priority] = CSS_PROP_RE.exec(line) || [];
// If this is ending with an unfinished line, add it onto the end
// with an empty value
if (!name && line && i > 0) {
name = line;
}
if (name) {
props.push({
name: name.trim(),
value: value || "",
priority: priority || ""
});
}
});
return props;
}
/**
* Event handler that causes a blur on the target if the input has
* multiple CSS properties as the value.
*/
function blurOnMultipleProperties(e) {
setTimeout(() => {
if (parseCSSText(e.target.value).length) {
let props = parseDeclarations(e.target.value);
if (props.length > 1) {
e.target.blur();
}
}, 0);

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

@ -55,7 +55,6 @@ support-files = browser_ruleview_pseudoelement.html
[browser_bug765105_background_image_tooltip.js]
[browser_bug889638_rule_view_color_picker.js]
[browser_bug726427_csstransform_tooltip.js]
[browser_bug940500_rule_view_pick_gradient_color.js]
[browser_ruleview_original_source_link.js]
support-files =
@ -67,3 +66,4 @@ support-files =
[browser_bug946331_close_tooltip_on_new_selection.js]
[browser_bug942297_user_property_reset.js]
[browser_styleinspector_outputparser.js]
[browser_bug970532_mathml_element.js]

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

@ -0,0 +1,70 @@
/* Any copyright", " is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Tests that the rule-view displays correctly on MathML elements
waitForExplicitFinish();
const TEST_URL = [
"data:text/html,",
"<div>",
" <math xmlns=\"http://www.w3.org/1998/Math/MathML\">",
" <mfrac>",
" <msubsup>",
" <mi>a</mi>",
" <mi>i</mi>",
" <mi>j</mi>",
" </msubsup>",
" <msub>",
" <mi>x</mi>",
" <mn>0</mn>",
" </msub>",
" </mfrac>",
" </math>",
"</div>"
].join("");
function test() {
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload(evt) {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
waitForFocus(runTests, content);
}, true);
content.location = TEST_URL;
}
function runTests() {
openRuleView((inspector, ruleView) => {
Task.spawn(function() {
info("Select the DIV node and verify the rule-view shows rules");
yield selectNode("div", inspector);
ok(ruleView.element.querySelectorAll(".ruleview-rule").length,
"The rule-view shows rules for the div element");
info("Select various MathML nodes and verify the rule-view is empty");
yield selectNode("math", inspector);
ok(!ruleView.element.querySelectorAll(".ruleview-rule").length,
"The rule-view is empty for the math element");
yield selectNode("msubsup", inspector);
ok(!ruleView.element.querySelectorAll(".ruleview-rule").length,
"The rule-view is empty for the msubsup element");
yield selectNode("mn", inspector);
ok(!ruleView.element.querySelectorAll(".ruleview-rule").length,
"The rule-view is empty for the mn element");
info("Select again the DIV node and verify the rule-view shows rules");
yield selectNode("div", inspector);
ok(ruleView.element.querySelectorAll(".ruleview-rule").length,
"The rule-view shows rules for the div element");
}).then(null, ok.bind(null, false)).then(finishUp);
});
}
function finishUp() {
gBrowser.removeCurrentTab();
finish();
}

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

@ -6,6 +6,8 @@ let doc;
let ruleWindow;
let ruleView;
let inspector;
let TEST_URL = 'url("http://example.com/browser/browser/devtools/' +
'styleinspector/test/test-image.png")';
function startTest()
{
@ -140,7 +142,7 @@ function testEditProperty()
let value = idRuleEditor.rule.domRule._rawStyle().getPropertyValue("border-color");
is(value, "red", "border-color should have been set.");
is(propEditor.isValid(), true, "red should be a valid entry");
finishTest();
testEditPropertyWithColon();
}));
});
@ -159,6 +161,43 @@ function testEditProperty()
ruleWindow);
}
function testEditPropertyWithColon()
{
let idRuleEditor = ruleView.element.children[1]._ruleEditor;
let propEditor = idRuleEditor.rule.textProps[0].editor;
waitForEditorFocus(propEditor.element, function onNewElement(aEditor) {
is(inplaceEditor(propEditor.nameSpan), aEditor, "Next focused editor should be the name editor.");
let input = aEditor.input;
waitForEditorFocus(propEditor.element, function onNewName(aEditor) {
promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {
input = aEditor.input;
is(inplaceEditor(propEditor.valueSpan), aEditor, "Focus should have moved to the value.");
waitForEditorBlur(aEditor, function() {
promiseDone(expectRuleChange(idRuleEditor.rule).then(() => {
let value = idRuleEditor.rule.domRule._rawStyle().getPropertyValue("background-image");
is(value, TEST_URL, "background-image should have been set.");
is(propEditor.isValid(), true, "the test URL should be a valid entry");
finishTest();
}));
});
for (let ch of (TEST_URL + ";")) {
EventUtils.sendChar(ch, ruleWindow);
}
}));
});
for (let ch of "background-image:") {
EventUtils.sendChar(ch, ruleWindow);
}
});
EventUtils.synthesizeMouse(propEditor.nameSpan, 32, 1,
{ },
ruleWindow);
}
function finishTest()
{
inspector = ruleWindow = ruleView = null;

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

@ -28,13 +28,15 @@ function selectNewElement()
let newElement = doc.createElement("div");
newElement.textContent = "Test Element";
doc.body.appendChild(newElement);
inspector.selection.setNode(newElement);
inspector.selection.setNode(newElement, "test");
let def = promise.defer();
ruleView.element.addEventListener("CssRuleViewRefreshed", function changed() {
ruleView.element.removeEventListener("CssRuleViewRefreshed", changed);
elementRuleEditor = ruleView.element.children[0]._ruleEditor;
def.resolve();
});
return def.promise;
}

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

@ -79,6 +79,42 @@ function openComputedView(callback)
});
}
/**
* Simple DOM node accesor function that takes either a node or a string css
* selector as argument and returns the corresponding node
* @param {String|DOMNode} nodeOrSelector
* @return {DOMNode}
*/
function getNode(nodeOrSelector)
{
let node = nodeOrSelector;
if (typeof nodeOrSelector === "string") {
node = content.document.querySelector(nodeOrSelector);
ok(node, "A node was found for selector " + nodeOrSelector);
}
return node;
}
/**
* Set the inspector's current selection to a node or to the first match of the
* given css selector
* @param {String|DOMNode} nodeOrSelector
* @param {InspectorPanel} inspector The instance of InspectorPanel currently loaded in the toolbox
* @param {String} reason Defaults to "test" which instructs the inspector not to highlight the node upon selection
* @return a promise that resolves when the inspector is updated with the new
* node
*/
function selectNode(nodeOrSelector, inspector, reason="test")
{
info("Selecting the node " + nodeOrSelector);
let node = getNode(nodeOrSelector);
let updated = inspector.once("inspector-updated");
inspector.selection.setNode(node, reason);
return updated;
}
function addStyle(aDocument, aString)
{
let node = aDocument.createElement('style');

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

@ -5,3 +5,4 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
BROWSER_CHROME_MANIFESTS += ['browser.ini']
XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']

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

@ -0,0 +1,206 @@
/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const Cu = Components.utils;
Cu.import("resource://gre/modules/devtools/Loader.jsm");
const {parseDeclarations} = devtools.require("devtools/styleinspector/css-parsing-utils");
const TEST_DATA = [
// Simple test
{
input: "p:v;",
expected: [{name: "p", value: "v", priority: ""}]
},
// Simple test
{
input: "this:is;a:test;",
expected: [
{name: "this", value: "is", priority: ""},
{name: "a", value: "test", priority: ""}
]
},
// Test a single declaration with semi-colon
{
input: "name:value;",
expected: [{name: "name", value: "value", priority: ""}]
},
// Test a single declaration without semi-colon
{
input: "name:value",
expected: [{name: "name", value: "value", priority: ""}]
},
// Test multiple declarations separated by whitespaces and carriage returns and tabs
{
input: "p1 : v1 ; \t\t \n p2:v2; \n\n\n\n\t p3 : v3;",
expected: [
{name: "p1", value: "v1", priority: ""},
{name: "p2", value: "v2", priority: ""},
{name: "p3", value: "v3", priority: ""},
]
},
// Test simple priority
{
input: "p1: v1; p2: v2 !important;",
expected: [
{name: "p1", value: "v1", priority: ""},
{name: "p2", value: "v2", priority: "important"}
]
},
// Test simple priority
{
input: "p1: v1 !important; p2: v2",
expected: [
{name: "p1", value: "v1", priority: "important"},
{name: "p2", value: "v2", priority: ""}
]
},
// Test simple priority
{
input: "p1: v1 ! important; p2: v2 ! important;",
expected: [
{name: "p1", value: "v1", priority: "important"},
{name: "p2", value: "v2", priority: "important"}
]
},
// Test invalid priority
{
input: "p1: v1 important;",
expected: [
{name: "p1", value: "v1 important", priority: ""}
]
},
// Test various types of background-image urls
{
input: "background-image: url(../../relative/image.png)",
expected: [{name: "background-image", value: "url(\"../../relative/image.png\")", priority: ""}]
},
{
input: "background-image: url(http://site.com/test.png)",
expected: [{name: "background-image", value: "url(\"http://site.com/test.png\")", priority: ""}]
},
{
input: "background-image: url(wow.gif)",
expected: [{name: "background-image", value: "url(\"wow.gif\")", priority: ""}]
},
// Test that urls with :;{} characters in them are parsed correctly
{
input: "background: red url(\"http://site.com/image{}:;.png?id=4#wat\") repeat top right",
expected: [
{name: "background", value: "red url(\"http://site.com/image{}:;.png?id=4#wat\") repeat top right", priority: ""}
]
},
// Test that an empty string results in an empty array
{input: "", expected: []},
// Test that a string comprised only of whitespaces results in an empty array
{input: " \n \n \n \n \t \t\t\t ", expected: []},
// Test that a null input throws an exception
{input: null, throws: true},
// Test that a undefined input throws an exception
{input: undefined, throws: true},
// Test that :;{} characters in quoted content are not parsed as multiple declarations
{
input: "content: \";color:red;}selector{color:yellow;\"",
expected: [
{name: "content", value: "\";color:red;}selector{color:yellow;\"", priority: ""}
]
},
// Test that rules aren't parsed, just declarations. So { and } found after a
// property name should be part of the property name, same for values.
{
input: "body {color:red;} p {color: blue;}",
expected: [
{name: "body {color", value: "red", priority: ""},
{name: "} p {color", value: "blue", priority: ""},
{name: "}", value: "", priority: ""}
]
},
// Test unbalanced : and ;
{
input: "color :red : font : arial;",
expected : [
{name: "color", value: "red : font : arial", priority: ""}
]
},
{input: "background: red;;;;;", expected: [{name: "background", value: "red", priority: ""}]},
{input: "background:;", expected: [{name: "background", value: "", priority: ""}]},
{input: ";;;;;", expected: []},
{input: ":;:;", expected: []},
// Test name only
{input: "color", expected: [
{name: "color", value: "", priority: ""}
]},
// Test trailing name without :
{input: "color:blue;font", expected: [
{name: "color", value: "blue", priority: ""},
{name: "font", value: "", priority: ""}
]},
// Test trailing name with :
{input: "color:blue;font:", expected: [
{name: "color", value: "blue", priority: ""},
{name: "font", value: "", priority: ""}
]},
// Test leading value
{input: "Arial;color:blue;", expected: [
{name: "", value: "Arial", priority: ""},
{name: "color", value: "blue", priority: ""}
]},
// Test hex colors
{input: "color: #333", expected: [{name: "color", value: "#333", priority: ""}]},
{input: "color: #456789", expected: [{name: "color", value: "#456789", priority: ""}]},
{input: "wat: #XYZ", expected: [{name: "wat", value: "#XYZ", priority: ""}]},
// Test string/url quotes escaping
{input: "content: \"this is a 'string'\"", expected: [{name: "content", value: "\"this is a 'string'\"", priority: ""}]},
{input: 'content: "this is a \\"string\\""', expected: [{name: "content", value: '\'this is a "string"\'', priority: ""}]},
{input: "content: 'this is a \"string\"'", expected: [{name: "content", value: '\'this is a "string"\'', priority: ""}]},
{input: "content: 'this is a \\'string\\'", expected: [{name: "content", value: '"this is a \'string\'"', priority: ""}]},
{input: "content: 'this \\' is a \" really strange string'", expected: [{name: "content", value: '"this \' is a \" really strange string"', priority: ""}]},
{
input: "content: \"a not s\\\
o very long title\"",
expected: [
{name: "content", value: '"a not s\
o very long title"', priority: ""}
]
}
];
function run_test() {
for (let test of TEST_DATA) {
do_print("Test input string " + test.input);
let output;
try {
output = parseDeclarations(test.input);
} catch (e) {
do_print("parseDeclarations threw an exception with the given input string");
if (test.throws) {
do_print("Exception expected");
do_check_true(true);
} else {
do_print("Exception unexpected\n" + e);
do_check_true(false);
}
}
if (output) {
assertOutput(output, test.expected);
}
}
}
function assertOutput(actual, expected) {
if (actual.length === expected.length) {
for (let i = 0; i < expected.length; i ++) {
do_check_true(!!actual[i]);
do_print("Check that the output item has the expected name, value and priority");
do_check_eq(expected[i].name, actual[i].name);
do_check_eq(expected[i].value, actual[i].value);
do_check_eq(expected[i].priority, actual[i].priority);
}
} else {
for (let prop of actual) {
do_print("Actual output contained: {name: "+prop.name+", value: "+prop.value+", priority: "+prop.priority+"}");
}
do_check_eq(actual.length, expected.length);
}
}

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

@ -0,0 +1,76 @@
/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const Cu = Components.utils;
Cu.import("resource://gre/modules/devtools/Loader.jsm");
const {parseSingleValue} = devtools.require("devtools/styleinspector/css-parsing-utils");
const TEST_DATA = [
{input: null, throws: true},
{input: undefined, throws: true},
{input: "", expected: {value: "", priority: ""}},
{input: " \t \t \n\n ", expected: {value: "", priority: ""}},
{input: "blue", expected: {value: "blue", priority: ""}},
{input: "blue !important", expected: {value: "blue", priority: "important"}},
{input: "blue!important", expected: {value: "blue", priority: "important"}},
{input: "blue ! important", expected: {value: "blue", priority: "important"}},
{input: "blue ! important", expected: {value: "blue", priority: "important"}},
{input: "blue !", expected: {value: "blue", priority: ""}},
{input: "blue !mportant", expected: {value: "blue !mportant", priority: ""}},
{input: " blue !important ", expected: {value: "blue", priority: "important"}},
{
input: "url(\"http://url.com/whyWouldYouDoThat!important.png\") !important",
expected: {
value: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
priority: "important"
}
},
{
input: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
expected: {
value: "url(\"http://url.com/whyWouldYouDoThat!important.png\")",
priority: ""
}
},
{
input: "\"content!important\" !important",
expected: {
value: "\"content!important\"",
priority: "important"
}
},
{
input: "\"content!important\"",
expected: {
value: "\"content!important\"",
priority: ""
}
}
];
function run_test() {
for (let test of TEST_DATA) {
do_print("Test input value " + test.input);
try {
let output = parseSingleValue(test.input);
assertOutput(output, test.expected);
} catch (e) {
do_print("parseSingleValue threw an exception with the given input value");
if (test.throws) {
do_print("Exception expected");
do_check_true(true);
} else {
do_print("Exception unexpected\n" + e);
do_check_true(false);
}
}
}
}
function assertOutput(actual, expected) {
do_print("Check that the output has the expected value and priority");
do_check_eq(expected.value, actual.value);
do_check_eq(expected.priority, actual.priority);
}

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

@ -0,0 +1,7 @@
[DEFAULT]
head =
tail =
firefox-appdir = browser
[test_parseDeclarations.js]
[test_parseSingleValue.js]

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

@ -12,51 +12,36 @@
const TEST_URI = "data:text/html;charset=utf-8,<div style='font-size:3em;" +
"foobarCssParser:baz'>test CSS parser filter</div>";
function onContentLoaded()
{
browser.removeEventListener("load", onContentLoaded, true);
let HUD = HUDService.getHudByWindow(content);
let hudId = HUD.hudId;
let outputNode = HUD.outputNode;
HUD.jsterm.clearOutput();
waitForSuccess({
name: "css error displayed",
validatorFn: function()
{
return outputNode.textContent.indexOf("foobarCssParser") > -1;
},
successFn: function()
{
HUD.setFilterState("cssparser", false);
let msg = "the unknown CSS property warning is not displayed, " +
"after filtering";
testLogEntry(outputNode, "foobarCssParser", msg, true, true);
HUD.setFilterState("cssparser", true);
finishTest();
},
failureFn: finishTest,
});
}
/**
* Unit test for bug 589162:
* CSS filtering on the console does not work
*/
function test()
{
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
function test() {
Task.spawn(runner).then(finishTest);
openConsole(null, function() {
browser.addEventListener("load", onContentLoaded, true);
content.location.reload();
function* runner() {
let {tab} = yield loadTab(TEST_URI);
let hud = yield openConsole(tab);
// CSS warnings are disabled by default.
hud.setFilterState("cssparser", true);
hud.jsterm.clearOutput();
content.location.reload();
yield waitForMessages({
webconsole: hud,
messages: [{
text: "foobarCssParser",
category: CATEGORY_CSS,
severity: SEVERITY_WARNING,
}],
});
}, true);
}
hud.setFilterState("cssparser", false);
let msg = "the unknown CSS property warning is not displayed, " +
"after filtering";
testLogEntry(hud.outputNode, "foobarCssParser", msg, true, true);
}
}

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

@ -16,6 +16,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
"@mozilla.org/xre/app-info;1", "nsICrashReporter");
#endif
XPCOMUtils.defineLazyModuleGetter(this, "CrashMonitor",
"resource://gre/modules/CrashMonitor.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
"@mozilla.org/uuid-generator;1", "nsIUUIDGenerator");
@ -76,6 +79,25 @@ SessionStore.prototype = {
// swallow exception that occurs if metro-tabs measure is already set up
}
CrashMonitor.previousCheckpoints.then(checkpoints => {
let previousSessionCrashed = false;
if (checkpoints) {
// If the previous session finished writing the final state, we'll
// assume there was no crash.
previousSessionCrashed = !checkpoints["sessionstore-final-state-write-complete"];
} else {
// If no checkpoints are saved, this is the first run with CrashMonitor or the
// metroSessionCheckpoints file was corrupted/deleted, so fallback to defining
// a crash as init-ing with an unexpected previousExecutionState
// 1 == RUNNING, 2 == SUSPENDED
previousSessionCrashed = Services.metro.previousExecutionState == 1 ||
Services.metro.previousExecutionState == 2;
}
Services.telemetry.getHistogramById("SHUTDOWN_OK").add(!previousSessionCrashed);
});
try {
let shutdownWasUnclean = false;
@ -291,8 +313,8 @@ SessionStore.prototype = {
if (this._saveTimer) {
this._saveTimer.cancel();
this._saveTimer = null;
this.saveState();
}
this.saveState();
break;
case "browser:purge-session-history": // catch sanitization
this._clearDisk();
@ -644,6 +666,9 @@ SessionStore.prototype = {
let istream = converter.convertToInputStream(aData);
NetUtil.asyncCopy(istream, ostream, function(rc) {
if (Components.isSuccessCode(rc)) {
if (Services.startup.shuttingDown) {
Services.obs.notifyObservers(null, "sessionstore-final-state-write-complete", "");
}
Services.obs.notifyObservers(null, "sessionstore-state-write-complete", "");
}
});

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

@ -109,6 +109,10 @@
text-decoration: line-through;
}
.theme-light .ruleview-overridden {
-moz-text-decoration-color: #667380; /* Content (Text) - Dark Grey */
}
.styleinspector-propertyeditor {
border: 1px solid #CCC;
padding: 0;

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

@ -113,6 +113,10 @@
text-decoration: line-through;
}
.theme-light .ruleview-overridden {
-moz-text-decoration-color: #667380; /* Content (Text) - Dark Grey */
}
.styleinspector-propertyeditor {
border: 1px solid #CCC;
padding: 0;

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

@ -104,6 +104,15 @@
inset 0 1px rgb(196, 196, 196);
}
#customization-undo-reset {
padding-left: 12px;
padding-right: 12px;
%ifdef XP_MACOSX
padding-top: 6px;
%else
padding-top: 7px;
%endif
}
#main-window[customize-entered] #customization-panel-container {
background-image: url("chrome://browser/skin/customizableui/customizeMode-separatorHorizontal.png"),

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

@ -109,6 +109,10 @@
text-decoration: line-through;
}
.theme-light .ruleview-overridden {
-moz-text-decoration-color: #667380; /* Content (Text) - Dark Grey */
}
.styleinspector-propertyeditor {
border: 1px solid #CCC;
padding: 0;

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

@ -73,7 +73,7 @@ crashreporter
Always defined.
datareporting
Whether data reporting (MOZ_DATA_REPORTING) is enabled for this build.
Whether data reporting (MOZ_DATA_REPORTING) is enabled for this build.
Values are ``true`` and ``false``.
@ -86,6 +86,13 @@ debug
Always defined.
healthreport
Whether the Health Report feature is enabled.
Values are ``true`` and ``false``.
Always defined.
mozconfig
The path of the :ref:`mozconfig file <mozconfig>` used to produce this build.
@ -133,3 +140,16 @@ topsrcdir
Always defined.
wave
Whether Wave audio support is enabled.
Values are ``true`` and ``false``.
Always defined.
webm
Whether WebM support is enabled.
Values are ``true`` and ``false``.
Always defined.

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

@ -249,6 +249,7 @@
android:label="@string/crash_reporter_title"
android:icon="@drawable/crash_reporter"
android:theme="@style/Gecko"
android:exported="false"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="org.mozilla.gecko.reportCrash" />

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

@ -12,6 +12,7 @@ import com.squareup.picasso.Picasso;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
@ -50,9 +51,14 @@ public class PanelListRow extends TwoLineRow {
int imageIndex = cursor.getColumnIndexOrThrow(HomeItems.IMAGE_URL);
final String imageUrl = cursor.getString(imageIndex);
Picasso.with(getContext())
.load(imageUrl)
.error(R.drawable.favicon)
.into(mIcon);
final boolean hasImageUrl = !TextUtils.isEmpty(imageUrl);
mIcon.setVisibility(hasImageUrl ? View.VISIBLE : View.GONE);
if (hasImageUrl) {
Picasso.with(getContext())
.load(imageUrl)
.error(R.drawable.favicon)
.into(mIcon);
}
}
}

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

@ -56,6 +56,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "Prompt",
XPCOMUtils.defineLazyModuleGetter(this, "HelperApps",
"resource://gre/modules/HelperApps.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SSLExceptions",
"resource://gre/modules/SSLExceptions.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
"resource://gre/modules/FormHistory.jsm");
@ -289,6 +292,14 @@ var BrowserApp = {
dump("zerdatime " + Date.now() + " - browser chrome startup finished.");
this.deck = document.getElementById("browsers");
this.deck.addEventListener("DOMContentLoaded", function BrowserApp_delayedStartup() {
try {
BrowserApp.deck.removeEventListener("DOMContentLoaded", BrowserApp_delayedStartup, false);
Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
sendMessageToJava({ type: "Gecko:DelayedStartup" });
} catch(ex) { console.log(ex); }
}, false);
BrowserEventHandler.init();
ViewportHandler.init();
@ -3790,8 +3801,9 @@ Tab.prototype = {
}
// Show page actions for helper apps.
if (BrowserApp.selectedTab == this)
ExternalApps.updatePageAction(this.browser.currentURI);
let uri = this.browser.currentURI;
if (BrowserApp.selectedTab == this && ExternalApps.shouldCheckUri(uri))
ExternalApps.updatePageAction(uri);
if (!Reader.isEnabledForParseOnLoad)
return;
@ -3800,7 +3812,7 @@ Tab.prototype = {
Reader.parseDocumentFromTab(this.id, function (article) {
// Do nothing if there's no article or the page in this tab has
// changed
let tabURL = this.browser.currentURI.specIgnoringRef;
let tabURL = uri.specIgnoringRef;
if (article == null || (article.url != tabURL)) {
// Don't clear the article for about:reader pages since we want to
// use the article from the previous page
@ -8004,6 +8016,14 @@ var ExternalApps = {
HelperApps.launchUri(uri);
},
shouldCheckUri: function(uri) {
if (!(uri.schemeIs("http") || uri.schemeIs("https") || uri.schemeIs("file"))) {
return false;
}
return true;
},
updatePageAction: function updatePageAction(uri) {
let apps = HelperApps.getAppsForUri(uri);

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

@ -12,7 +12,6 @@
<script type="application/javascript" src="chrome://browser/content/browser.js"/>
<script type="application/javascript" src="chrome://browser/content/downloads.js"/>
<script type="application/javascript" src="chrome://browser/content/exceptions.js"/>
<deck id="browsers" flex="1"/>

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

@ -34,7 +34,6 @@ chrome.jar:
* content/browser.js (content/browser.js)
content/bindings/checkbox.xml (content/bindings/checkbox.xml)
content/bindings/settings.xml (content/bindings/settings.xml)
content/exceptions.js (content/exceptions.js)
content/downloads.js (content/downloads.js)
content/netError.xhtml (content/netError.xhtml)
content/SelectHelper.js (content/SelectHelper.js)

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

@ -88,6 +88,9 @@ BrowserCLH.prototype = {
if (!uri)
return;
// Let's get a head start on opening the network connection to the URI we are about to load
Services.io.QueryInterface(Ci.nsISpeculativeConnect).speculativeConnect(uri, null);
let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
if (browserWin) {
if (!pinned) {

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

@ -1,6 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict"
let Cc = Components.classes;
let Ci = Components.interfaces;
@ -8,6 +9,8 @@ let Cu = Components.utils;
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
this.EXPORTED_SYMBOLS = ["SSLExceptions"];
/**
A class to add exceptions to override SSL certificate problems. The functionality
itself is borrowed from exceptionDialog.js.
@ -48,8 +51,8 @@ SSLExceptions.prototype = {
*/
_checkCert: function SSLE_checkCert(aURI) {
this._sslStatus = null;
let req = new XMLHttpRequest();
let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
try {
if (aURI) {
req.open("GET", aURI.prePath, false);

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

@ -17,6 +17,7 @@ EXTRA_JS_MODULES += [
'Sanitizer.jsm',
'SharedPreferences.jsm',
'SimpleServiceDiscovery.jsm',
'SSLExceptions.jsm',
]
if CONFIG['MOZ_ANDROID_SYNTHAPKS']:

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

@ -78,6 +78,7 @@ def build_dict(config, env=os.environ):
d['debug'] = substs.get('MOZ_DEBUG') == '1'
d['crashreporter'] = bool(substs.get('MOZ_CRASHREPORTER'))
d['datareporting'] = bool(substs.get('MOZ_DATA_REPORTING'))
d['healthreport'] = substs.get('MOZ_SERVICES_HEALTHREPORT') == '1'
d['asan'] = substs.get('MOZ_ASAN') == '1'
d['tests_enabled'] = substs.get('ENABLE_TESTS') == "1"
d['bin_suffix'] = substs.get('BIN_SUFFIX', '')

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

@ -15,8 +15,7 @@ Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/Credentials.jsm");
// Default can be changed by the preference 'identity.fxaccounts.auth.uri'
let _host = "https://api-accounts.dev.lcip.org/v1";
let _host = "https://api.accounts.firefox.com/v1"
try {
_host = Services.prefs.getCharPref("identity.fxaccounts.auth.uri");
} catch(keepDefault) {}

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

@ -1313,6 +1313,74 @@ Example
"google.urlbar": 7
},
org.mozilla.sync.sync
---------------------
This daily measurement contains information about the Sync service.
Values should be recorded for every day FHR measurements occurred.
Version 1
^^^^^^^^^
This version debuted with Firefox 30 on desktop. It contains the following
properties:
enabled
Daily numeric indicating whether Sync is configured and enabled. 1 if so,
0 otherwise.
preferredProtocol
String version of the maximum Sync protocol version the client supports.
This will be ``1.1`` for for legacy Sync and ``1.5`` for clients that
speak the Firefox Accounts protocol.
actualProtocol
The actual Sync protocol version the client is configured to use.
This will be ``1.1`` if the client is configured with the legacy Sync
service or if the client only supports ``1.1``.
It will be ``1.5`` if the client supports ``1.5`` and either a) the
client is not configured b) the client is using Firefox Accounts Sync.
syncStart
Count of sync operations performed.
syncSuccess
Count of sync operations that completed successfully.
syncError
Count of sync operations that did not complete successfully.
This is a measure of overall sync success. This does *not* reflect
recoverable errors (such as record conflict) that can occur during
sync. This is thus a rough proxy of whether the sync service is
operating without error.
org.mozilla.sync.devices
------------------------
This daily measurement contains information about the device type composition
for the configured Sync account.
Version 1
^^^^^^^^^
Version 1 was introduced with Firefox 30.
Field names are dynamic according to the client-reported device types from
Sync records. All fields are daily last seen integer values corresponding to
the number of devices of that type.
Common values include:
desktop
Corresponds to a Firefox desktop client.
mobile
Corresponds to a Fennec client.
org.mozilla.sysinfo.sysinfo
---------------------------

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

@ -160,10 +160,14 @@ this.ProviderManager.prototype = Object.freeze({
* @return Promise<null>
*/
registerProvider: function (provider) {
if (!(provider instanceof Provider)) {
throw new Error("Argument must be a Provider instance.");
// We should perform an instanceof check here. However, due to merged
// compartments, the Provider type may belong to one of two JSMs
// isinstance gets confused depending on which module Provider comes
// from. Some code references Provider from dataprovider.jsm; others from
// Metrics.jsm.
if (!provider.name) {
throw new Error("Provider is not valid: does not have a name.");
}
if (this._providers.has(provider.name)) {
return CommonUtils.laterTickResolvingPromise();
}

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

@ -47,7 +47,7 @@ add_task(function test_register_provider() {
try {
manager.registerProvider({});
} catch (ex) {
do_check_true(ex.message.startsWith("Argument must be a Provider"));
do_check_true(ex.message.startsWith("Provider is not valid"));
failed = true;
} finally {
do_check_true(failed);

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

@ -20,6 +20,7 @@ sync_modules := \
addonutils.js \
browserid_identity.js \
engines.js \
healthreport.jsm \
identity.js \
jpakeclient.js \
keys.js \

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

@ -23,3 +23,7 @@ contract @mozilla.org/network/protocol/about;1?what=sync-log {d28f8a0b-95da-48f4
# Register resource aliases
# (Note, for tests these are also set up in addResourceAlias)
resource services-sync resource://gre/modules/services-sync/
#ifdef MOZ_SERVICES_HEALTHREPORT
category healthreport-js-provider-default SyncProvider resource://services-sync/healthreport.jsm
#endif

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

@ -92,6 +92,11 @@ WeaveService.prototype = {
return deferred.promise;
},
/**
* Whether Firefox Accounts is enabled.
*
* @return bool
*/
get fxAccountsEnabled() {
// work out what identity manager to use. This is stored in a preference;
// if the preference exists, we trust it.
@ -112,6 +117,21 @@ WeaveService.prototype = {
return fxAccountsEnabled;
},
/**
* Whether Sync appears to be enabled.
*
* This returns true if all the Sync preferences for storing account
* and server configuration are populated.
*
* It does *not* perform a robust check to see if the client is working.
* For that, you'll want to check Weave.Status.checkSetup().
*/
get enabled() {
let prefs = Services.prefs.getBranch(SYNC_PREFS_BRANCH);
return prefs.prefHasUserValue("username") &&
prefs.prefHasUserValue("clusterURL");
},
observe: function (subject, topic, data) {
switch (topic) {
case "app-startup":

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

@ -71,6 +71,28 @@ ClientEngine.prototype = {
return stats;
},
/**
* Obtain information about device types.
*
* Returns a Map of device types to integer counts.
*/
get deviceTypes() {
let counts = new Map();
counts.set(this.localType, 1);
for each (let record in this._store._remoteClients) {
let type = record.type;
if (!counts.has(type)) {
counts.set(type, 0);
}
counts.set(type, counts.get(type) + 1);
}
return counts;
},
get localID() {
// Generate a random GUID id we don't have one
let localID = Svc.Prefs.get("client.GUID", "");

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

@ -0,0 +1,180 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = [
"SyncProvider",
];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/Metrics.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
const DAILY_LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC};
const DAILY_LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_TEXT};
const DAILY_COUNTER_FIELD = {type: Metrics.Storage.FIELD_DAILY_COUNTER};
XPCOMUtils.defineLazyModuleGetter(this, "Weave",
"resource://services-sync/main.js");
function SyncMeasurement1() {
Metrics.Measurement.call(this);
}
SyncMeasurement1.prototype = Object.freeze({
__proto__: Metrics.Measurement.prototype,
name: "sync",
version: 1,
fields: {
enabled: DAILY_LAST_NUMERIC_FIELD,
preferredProtocol: DAILY_LAST_TEXT_FIELD,
activeProtocol: DAILY_LAST_TEXT_FIELD,
syncStart: DAILY_COUNTER_FIELD,
syncSuccess: DAILY_COUNTER_FIELD,
syncError: DAILY_COUNTER_FIELD,
},
});
function SyncDevicesMeasurement1() {
Metrics.Measurement.call(this);
}
SyncDevicesMeasurement1.prototype = Object.freeze({
__proto__: Metrics.Measurement.prototype,
name: "devices",
version: 1,
fields: {},
shouldIncludeField: function (name) {
return true;
},
fieldType: function (name) {
return Metrics.Storage.FIELD_DAILY_COUNTER;
},
});
this.SyncProvider = function () {
Metrics.Provider.call(this);
};
SyncProvider.prototype = Object.freeze({
__proto__: Metrics.Provider.prototype,
name: "org.mozilla.sync",
measurementTypes: [
SyncDevicesMeasurement1,
SyncMeasurement1,
],
_OBSERVERS: [
"weave:service:sync:start",
"weave:service:sync:finish",
"weave:service:sync:error",
],
postInit: function () {
for (let o of this._OBSERVERS) {
Services.obs.addObserver(this, o, false);
}
return Promise.resolve();
},
onShutdown: function () {
for (let o of this._OBSERVERS) {
Services.obs.removeObserver(this, o);
}
return Promise.resolve();
},
observe: function (subject, topic, data) {
let field;
switch (topic) {
case "weave:service:sync:start":
field = "syncStart";
break;
case "weave:service:sync:finish":
field = "syncSuccess";
break;
case "weave:service:sync:error":
field = "syncError";
break;
}
let m = this.getMeasurement(SyncMeasurement1.prototype.name,
SyncMeasurement1.prototype.version);
return this.enqueueStorageOperation(function recordSyncEvent() {
return m.incrementDailyCounter(field);
});
},
collectDailyData: function () {
return this.storage.enqueueTransaction(this._populateDailyData.bind(this));
},
_populateDailyData: function* () {
let m = this.getMeasurement(SyncMeasurement1.prototype.name,
SyncMeasurement1.prototype.version);
let svc = Cc["@mozilla.org/weave/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject;
let enabled = svc.enabled;
yield m.setDailyLastNumeric("enabled", enabled ? 1 : 0);
// preferredProtocol is constant and only changes as the client
// evolves.
yield m.setDailyLastText("preferredProtocol", "1.5");
let protocol = svc.fxAccountsEnabled ? "1.5" : "1.1";
yield m.setDailyLastText("activeProtocol", protocol);
if (!enabled) {
return;
}
// Before grabbing more information, be sure the Sync service
// is fully initialized. This has the potential to initialize
// Sync on the spot. This may be undesired if Sync appears to
// be enabled but it really isn't. That responsibility should
// be up to svc.enabled to not return false positives, however.
yield svc.whenLoaded();
if (Weave.Status.service != Weave.STATUS_OK) {
return;
}
// Device types are dynamic. So we need to dynamically create fields if
// they don't exist.
let dm = this.getMeasurement(SyncDevicesMeasurement1.prototype.name,
SyncDevicesMeasurement1.prototype.version);
let devices = Weave.Service.clientsEngine.deviceTypes;
for (let [field, count] of devices) {
let hasField = this.storage.hasFieldFromMeasurement(dm.id, field,
this.storage.FIELD_DAILY_LAST_NUMERIC);
let fieldID;
if (hasField) {
fieldID = this.storage.fieldIDFromMeasurement(dm.id, field);
} else {
fieldID = yield this.storage.registerField(dm.id, field,
this.storage.FIELD_DAILY_LAST_NUMERIC);
}
yield this.storage.setDailyLastNumericFromFieldID(fieldID, count);
}
},
});

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

@ -8,6 +8,9 @@ DIRS += ['locales']
TEST_DIRS += ['tests']
EXTRA_COMPONENTS += [
'SyncComponents.manifest',
'Weave.js',
]
EXTRA_PP_COMPONENTS += [
'SyncComponents.manifest',
]

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

@ -0,0 +1,203 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://gre/modules/Metrics.jsm", this);
Cu.import("resource://gre/modules/Preferences.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://services-sync/main.js", this);
Cu.import("resource://services-sync/healthreport.jsm", this);
Cu.import("resource://testing-common/services-common/logging.js", this);
Cu.import("resource://testing-common/services/healthreport/utils.jsm", this);
function run_test() {
initTestLogging();
// A head JS file always sets the
// services.sync.fxaccounts.enabled pref. This prevents us from testing
// pristine profile conditions and likely indicates there isn't test
// coverage of the Sync service's fxAccountsEnabled property. Check
// that pre-condition and hack around it.
let branch = new Preferences("services.sync.");
Assert.ok(branch.isSet("fxaccounts.enabled"), "Check precondition");
branch.reset("fxaccounts.enabled");
run_next_test();
}
add_task(function test_constructor() {
let provider = new SyncProvider();
});
// Provider can initialize and de-initialize properly.
add_task(function* test_init() {
let storage = yield Metrics.Storage("init");
let provider = new SyncProvider();
yield provider.init(storage);
yield provider.shutdown();
yield storage.close();
});
add_task(function* test_collect() {
let storage = yield Metrics.Storage("collect");
let provider = new SyncProvider();
yield provider.init(storage);
// Initially nothing should be configured.
let now = new Date();
yield provider.collectDailyData();
let m = provider.getMeasurement("sync", 1);
let values = yield m.getValues();
Assert.equal(values.days.size, 1);
Assert.ok(values.days.hasDay(now));
let day = values.days.getDay(now);
Assert.ok(day.has("enabled"));
Assert.ok(day.has("activeProtocol"));
Assert.ok(day.has("preferredProtocol"));
Assert.equal(day.get("enabled"), 0);
Assert.equal(day.get("preferredProtocol"), "1.5");
Assert.equal(day.get("activeProtocol"), "1.5",
"Protocol without setup should be FX Accounts version.");
// Now check for old Sync setup.
let branch = new Preferences("services.sync.");
branch.set("username", "foo");
branch.reset("fxaccounts.enabled");
yield provider.collectDailyData();
values = yield m.getValues();
Assert.equal(values.days.getDay(now).get("activeProtocol"), "1.1",
"Protocol with old Sync setup is correct.");
Assert.equal(Weave.Status.__authManager, undefined, "Detect code changes");
// Let's enable Sync so we can get more useful data.
// We need to do this because the FHR probe only records more info if Sync
// is configured properly.
Weave.Service.identity.account = "johndoe";
Weave.Service.identity.basicPassword = "ilovejane";
Weave.Service.identity.syncKey = Weave.Utils.generatePassphrase();
Weave.Service.clusterURL = "http://localhost/";
Assert.equal(Weave.Status.checkSetup(), Weave.STATUS_OK);
yield provider.collectDailyData();
values = yield m.getValues();
day = values.days.getDay(now);
Assert.equal(day.get("enabled"), 1);
// An empty account should have 1 device: us.
let dm = provider.getMeasurement("devices", 1);
values = yield dm.getValues();
Assert.ok(values.days.hasDay(now));
day = values.days.getDay(now);
Assert.equal(day.size, 1);
let engine = Weave.Service.clientsEngine;
Assert.ok(engine);
Assert.ok(day.has(engine.localType));
Assert.equal(day.get(engine.localType), 1);
// Add some devices and ensure they show up.
engine._store._remoteClients["id1"] = {type: "mobile"};
engine._store._remoteClients["id2"] = {type: "tablet"};
engine._store._remoteClients["id3"] = {type: "mobile"};
yield provider.collectDailyData();
values = yield dm.getValues();
day = values.days.getDay(now);
let expected = {
"foobar": 0,
"tablet": 1,
"mobile": 2,
"desktop": 0,
};
for (let type in expected) {
let count = expected[type];
if (engine.localType == type) {
count++;
}
if (!count) {
Assert.ok(!day.has(type));
} else {
Assert.ok(day.has(type));
Assert.equal(day.get(type), count);
}
}
engine._store._remoteClients = {};
yield provider.shutdown();
yield storage.close();
});
add_task(function* test_sync_events() {
let storage = yield Metrics.Storage("sync_events");
let provider = new SyncProvider();
yield provider.init(storage);
let m = provider.getMeasurement("sync", 1);
for (let i = 0; i < 5; i++) {
Services.obs.notifyObservers(null, "weave:service:sync:start", null);
}
for (let i = 0; i < 3; i++) {
Services.obs.notifyObservers(null, "weave:service:sync:finish", null);
}
for (let i = 0; i < 2; i++) {
Services.obs.notifyObservers(null, "weave:service:sync:error", null);
}
// Wait for storage to complete.
yield m.storage.enqueueOperation(() => {
return Promise.resolve();
});
let values = yield m.getValues();
let now = new Date();
Assert.ok(values.days.hasDay(now));
let day = values.days.getDay(now);
Assert.ok(day.has("syncStart"));
Assert.ok(day.has("syncSuccess"));
Assert.ok(day.has("syncError"));
Assert.equal(day.get("syncStart"), 5);
Assert.equal(day.get("syncSuccess"), 3);
Assert.equal(day.get("syncError"), 2);
yield provider.shutdown();
yield storage.close();
});
add_task(function* test_healthreporter_json() {
let reporter = yield getHealthReporter("healthreporter_json");
yield reporter.init();
try {
yield reporter._providerManager.registerProvider(new SyncProvider());
yield reporter.collectMeasurements();
let payload = yield reporter.getJSONPayload(true);
let now = new Date();
let today = reporter._formatDate(now);
Assert.ok(today in payload.data.days);
let day = payload.data.days[today];
Assert.ok("org.mozilla.sync.sync" in day);
Assert.ok("org.mozilla.sync.devices" in day);
let devices = day["org.mozilla.sync.devices"];
let engine = Weave.Service.clientsEngine;
Assert.ok(engine);
let type = engine.localType;
Assert.ok(type);
Assert.ok(type in devices);
Assert.equal(devices[type], 1);
} finally {
reporter._shutdown();
}
});

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

@ -13,8 +13,14 @@ function run_test() {
_("When imported, Service.onStartup is called");
initTestLogging("Trace");
let xps = Cc["@mozilla.org/weave/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject;
do_check_false(xps.enabled);
// Test fixtures
Service.identity.username = "johndoe";
do_check_false(xps.enabled);
Cu.import("resource://services-sync/service.js");
@ -29,10 +35,6 @@ function run_test() {
_("Observers are notified of startup");
do_test_pending();
let xps = Cc["@mozilla.org/weave/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject;
do_check_false(Service.status.ready);
do_check_false(xps.ready);
Observers.add("weave:service:ready", function (subject, data) {
@ -43,4 +45,10 @@ function run_test() {
Svc.Prefs.resetBranch("");
do_test_finished();
});
do_check_false(xps.enabled);
Service.identity.account = "johndoe";
Service.clusterURL = "http://localhost/";
do_check_true(xps.enabled);
}

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

@ -164,3 +164,6 @@ skip-if = debug
[test_tab_engine.js]
[test_tab_store.js]
[test_tab_tracker.js]
[test_healthreport.js]
skip-if = ! healthreport

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

@ -46,8 +46,9 @@ class InvalidTestPathError(Exception):
class XPCShellRunner(MozbuildObject):
"""Run xpcshell tests."""
def run_suite(self, **kwargs):
manifest = os.path.join(self.topobjdir, '_tests', 'xpcshell',
'xpcshell.ini')
from manifestparser import TestManifest
manifest = TestManifest(manifests=[os.path.join(self.topobjdir,
'_tests', 'xpcshell', 'xpcshell.ini')])
return self._run_xpcshell_harness(manifest=manifest, **kwargs)

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

@ -86,9 +86,13 @@ let CrashMonitorInternal = {
* Path to checkpoint file.
*
* Each time a new notification is received, this file is written to
* disc to reflect the information in |checkpoints|.
* disc to reflect the information in |checkpoints|. Although Firefox for
* Desktop and Metro share the same profile, they need to keep record of
* crashes separately.
*/
path: OS.Path.join(OS.Constants.Path.profileDir, "sessionCheckpoints.json"),
path: (Services.metro && Services.metro.immersive) ?
OS.Path.join(OS.Constants.Path.profileDir, "metro", "sessionCheckpoints.json"):
OS.Path.join(OS.Constants.Path.profileDir, "sessionCheckpoints.json"),
/**
* Load checkpoints from previous session asynchronously.
@ -181,6 +185,7 @@ this.CrashMonitor = {
);
CrashMonitorInternal.initialized = true;
OS.File.makeDir(OS.Path.join(OS.Constants.Path.profileDir, "metro"));
return promise;
},

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

@ -50,6 +50,7 @@ Cu.import("resource://gre/modules/osfile/ospath.jsm", Path);
// The library of promises.
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
// The implementation of communications
Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
@ -83,6 +84,9 @@ const EXCEPTION_CONSTRUCTORS = {
},
URIError: function(error) {
return new URIError(error.message, error.fileName, error.lineNumber);
},
OSError: function(error) {
return OS.File.Error.fromMsg(error);
}
};
@ -145,9 +149,25 @@ let Scheduler = {
shutdown: false,
/**
* The latest promise returned.
* A promise resolved once all operations are complete.
*
* This promise is never rejected and the result is always undefined.
*/
latestPromise: Promise.resolve("OS.File scheduler hasn't been launched yet"),
queue: Promise.resolve(),
/**
* The latest message sent and still waiting for a reply. This
* field is stored only in DEBUG builds, to avoid hoarding memory in
* release builds.
*/
latestSent: undefined,
/**
* The latest reply received, or null if we are waiting for a reply.
* This field is stored only in DEBUG builds, to avoid hoarding
* memory in release builds.
*/
latestReceived: undefined,
/**
* A timer used to automatically shut down the worker after some time.
@ -169,6 +189,32 @@ let Scheduler = {
this.resetTimer = setTimeout(File.resetWorker, delay);
},
/**
* Push a task at the end of the queue.
*
* @param {function} code A function returning a Promise.
* This function will be executed once all the previously
* pushed tasks have completed.
* @return {Promise} A promise with the same behavior as
* the promise returned by |code|.
*/
push: function(code) {
let promise = this.queue.then(code);
// By definition, |this.queue| can never reject.
this.queue = promise.then(null, () => undefined);
// Fork |promise| to ensure that uncaught errors are reported
return promise.then(null, null);
},
/**
* Post a message to the worker thread.
*
* @param {string} method The name of the method to call.
* @param {...} args The arguments to pass to the method. These arguments
* must be clonable.
* @return {Promise} A promise conveying the result/error caused by
* calling |method| with arguments |args|.
*/
post: function post(method, ...args) {
if (this.shutdown) {
LOG("OS.File is not available anymore. The following request has been rejected.",
@ -194,77 +240,71 @@ let Scheduler = {
if (methodArgs) {
options = methodArgs[methodArgs.length - 1];
}
let promise = worker.post(method,...args);
return this.latestPromise = promise.then(
function onSuccess(data) {
if (firstLaunch) {
Scheduler._updateTelemetry();
}
// Don't restart the timer when reseting the worker, since that will
// lead to an endless "resetWorker()" loop.
if (method != "Meta_reset") {
Scheduler.restartTimer();
}
// Check for duration and return result.
if (!options) {
return data.ok;
}
// Check for options.outExecutionDuration.
if (typeof options !== "object" ||
!("outExecutionDuration" in options)) {
return data.ok;
}
// If data.durationMs is not present, return data.ok (there was an
// exception applying the method).
if (!("durationMs" in data)) {
return data.ok;
}
// Bug 874425 demonstrates that two successive calls to Date.now()
// can actually produce an interval with negative duration.
// We assume that this is due to an operation that is so short
// that Date.now() is not monotonic, so we round this up to 0.
let durationMs = Math.max(0, data.durationMs);
// Accumulate (or initialize) outExecutionDuration
if (typeof options.outExecutionDuration == "number") {
options.outExecutionDuration += durationMs;
} else {
options.outExecutionDuration = durationMs;
}
return data.ok;
},
function onError(error) {
if (firstLaunch) {
Scheduler._updateTelemetry();
}
// Don't restart the timer when reseting the worker, since that will
// lead to an endless "resetWorker()" loop.
if (method != "Meta_reset") {
Scheduler.restartTimer();
}
// Check and throw EvalError | InternalError | RangeError
// | ReferenceError | SyntaxError | TypeError | URIError
if (error.data && error.data.exn in EXCEPTION_CONSTRUCTORS) {
throw EXCEPTION_CONSTRUCTORS[error.data.exn](error.data);
}
// Decode any serialized error
if (error instanceof PromiseWorker.WorkerError) {
throw OS.File.Error.fromMsg(error.data);
}
// Extract something meaningful from ErrorEvent
if (error instanceof ErrorEvent) {
let message = error.message;
if (message == "uncaught exception: [object StopIteration]") {
throw StopIteration;
}
throw new Error(message, error.filename, error.lineno);
}
throw error;
return this.push(() => Task.spawn(function*() {
if (OS.Constants.Sys.DEBUG) {
// Update possibly memory-expensive debugging information
Scheduler.latestReceived = null;
Scheduler.latestSent = [method, ...args];
}
);
let data;
let reply;
try {
data = yield worker.post(method, ...args);
reply = data;
} catch (error if error instanceof PromiseWorker.WorkerError) {
reply = error;
throw EXCEPTION_CONSTRUCTORS[error.data.exn || "OSError"](error.data);
} catch (error if error instanceof ErrorEvent) {
reply = error;
let message = error.message;
if (message == "uncaught exception: [object StopIteration]") {
throw StopIteration;
}
throw new Error(message, error.filename, error.lineno);
} finally {
if (OS.Constants.Sys.DEBUG) {
// Update possibly memory-expensive debugging information
Scheduler.latestSent = null;
Scheduler.latestReceived = reply;
}
if (firstLaunch) {
Scheduler._updateTelemetry();
}
// Don't restart the timer when reseting the worker, since that will
// lead to an endless "resetWorker()" loop.
if (method != "Meta_reset") {
Scheduler.restartTimer();
}
}
// Check for duration and return result.
if (!options) {
return data.ok;
}
// Check for options.outExecutionDuration.
if (typeof options !== "object" ||
!("outExecutionDuration" in options)) {
return data.ok;
}
// If data.durationMs is not present, return data.ok (there was an
// exception applying the method).
if (!("durationMs" in data)) {
return data.ok;
}
// Bug 874425 demonstrates that two successive calls to Date.now()
// can actually produce an interval with negative duration.
// We assume that this is due to an operation that is so short
// that Date.now() is not monotonic, so we round this up to 0.
let durationMs = Math.max(0, data.durationMs);
// Accumulate (or initialize) outExecutionDuration
if (typeof options.outExecutionDuration == "number") {
options.outExecutionDuration += durationMs;
} else {
options.outExecutionDuration = durationMs;
}
return data.ok;
}));
},
/**
@ -1258,8 +1298,18 @@ this.OS.Path = Path;
// clients should register using AsyncShutdown.addBlocker.
AsyncShutdown.profileBeforeChange.addBlocker(
"OS.File: flush I/O queued before profile-before-change",
() =>
// Wait until the latest currently enqueued promise is satisfied/rejected
Scheduler.latestPromise.then(null,
function onError() { /* ignore error */})
// Wait until the latest currently enqueued promise is satisfied/rejected
(() => Scheduler.queue),
function getDetails() {
let result = {
launched: Scheduler.launched,
shutdown: Scheduler.shutdown,
pendingReset: !!Scheduler.resetTimer,
};
if (OS.Constants.Sys.DEBUG) {
result.latestSent = Scheduler.latestSent;
result.latestReceived - Scheduler.latestReceived;
};
return result;
}
);

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

@ -23,13 +23,14 @@ Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/TelemetryFile.jsm", this);
Cu.import("resource://gre/modules/TelemetryPing.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
let {OS: {File, Path, Constants}} = Cu.import("resource://gre/modules/osfile.jsm", {});
// We increment TelemetryFile's MAX_PING_FILE_AGE and
// OVERDUE_PING_FILE_AGE by 1ms so that our test pings exceed
// those points in time.
const EXPIRED_PING_FILE_AGE = TelemetryFile.MAX_PING_FILE_AGE + 1;
const OVERDUE_PING_FILE_AGE = TelemetryFile.OVERDUE_PING_FILE_AGE + 1;
// OVERDUE_PING_FILE_AGE by 1 minute so that our test pings exceed
// those points in time, even taking into account file system imprecision.
const ONE_MINUTE_MS = 60 * 1000;
const EXPIRED_PING_FILE_AGE = TelemetryFile.MAX_PING_FILE_AGE + ONE_MINUTE_MS;
const OVERDUE_PING_FILE_AGE = TelemetryFile.OVERDUE_PING_FILE_AGE + ONE_MINUTE_MS;
const PING_SAVE_FOLDER = "saved-telemetry-pings";
const PING_TIMEOUT_LENGTH = 5000;
@ -52,7 +53,8 @@ let gSeenPings = 0;
* @param aAge the age in milliseconds to offset from now. A value
* of 10 would make the ping 10ms older than now, for
* example.
* @returns an Array with the created pings.
* @returns Promise
* @resolve an Array with the created pings.
*/
function createSavedPings(aNum, aAge) {
return Task.spawn(function*(){
@ -71,8 +73,8 @@ function createSavedPings(aNum, aAge) {
if (aAge) {
// savePing writes to the file synchronously, so we're good to
// modify the lastModifedTime now.
let file = getSaveFileForPing(ping);
file.lastModifiedTime = age;
let file = getSavePathForPing(ping);
yield File.setDates(file, null, age);
}
gCreatedPings++;
pings.push(ping);
@ -86,31 +88,29 @@ function createSavedPings(aNum, aAge) {
* exist.
*
* @param aPings an Array of pings to delete.
* @returns Promise
*/
function clearPings(aPings) {
for (let ping of aPings) {
let file = getSaveFileForPing(ping);
if (file.exists()) {
file.remove(false);
return Task.spawn(function*() {
for (let ping of aPings) {
let path = getSavePathForPing(ping);
yield File.remove(path);
}
}
});
}
/**
* Returns a handle for the file that aPing should be
* stored in locally.
*
* @returns nsILocalFile
* @returns path
*/
function getSaveFileForPing(aPing) {
let file = Services.dirsvc.get("ProfD", Ci.nsILocalFile).clone();
file.append(PING_SAVE_FOLDER);
file.append(aPing.slug);
return file;
function getSavePathForPing(aPing) {
return Path.join(Constants.Path.profileDir, PING_SAVE_FOLDER, aPing.slug);
}
/**
* Check if the number of TelemetryPings received by the
* Check if the number of TelemetryPings received by the
* HttpServer is not equal to aExpectedNum.
*
* @param aExpectedNum the number of pings we expect to receive.
@ -123,18 +123,21 @@ function assertReceivedPings(aExpectedNum) {
* Throws if any pings in aPings is saved locally.
*
* @param aPings an Array of pings to check.
* @returns Promise
*/
function assertNotSaved(aPings) {
let saved = 0;
for (let ping of aPings) {
let file = getSaveFileForPing(ping);
if (file.exists()) {
saved++;
return Task.spawn(function*() {
let saved = 0;
for (let ping of aPings) {
let file = getSavePathForPing(ping);
if (yield File.exists()) {
saved++;
}
}
}
if (saved > 0) {
do_throw("Found " + saved + " unexpected saved pings.");
}
if (saved > 0) {
do_throw("Found " + saved + " unexpected saved pings.");
}
});
}
/**
@ -200,7 +203,7 @@ add_task(function test_expired_pings_are_deleted() {
let expiredPings = yield createSavedPings(EXPIRED_PINGS, EXPIRED_PING_FILE_AGE);
yield startTelemetry();
assertReceivedPings(0);
assertNotSaved(expiredPings);
yield assertNotSaved(expiredPings);
yield resetTelemetry();
});
@ -212,7 +215,7 @@ add_task(function test_recent_pings_not_sent() {
yield startTelemetry();
assertReceivedPings(0);
yield resetTelemetry();
clearPings(recentPings);
yield clearPings(recentPings);
});
/**
@ -228,9 +231,9 @@ add_task(function test_overdue_pings_trigger_send() {
yield startTelemetry();
assertReceivedPings(TOTAL_EXPECTED_PINGS);
assertNotSaved(recentPings);
assertNotSaved(expiredPings);
assertNotSaved(overduePings);
yield assertNotSaved(recentPings);
yield assertNotSaved(expiredPings);
yield assertNotSaved(overduePings);
yield resetTelemetry();
});

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

@ -22,9 +22,11 @@ function setup_crash() {
}
function after_crash(mdump, extra) {
do_print("after crash: " + extra.AsyncShutdownTimeout);
let info = JSON.parse(extra.AsyncShutdownTimeout);
do_check_true(info.phase == "testing-async-shutdown-crash");
do_check_true(info.conditions.indexOf("A blocker that is never satisfied") != -1);
do_check_eq(info.phase, "testing-async-shutdown-crash");
do_print("Condition: " + JSON.stringify(info.conditions));
do_check_true(JSON.stringify(info.conditions).indexOf("A blocker that is never satisfied") != -1);
}
function run_test() {

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

@ -357,6 +357,10 @@ var PageStyleActor = protocol.ActorClass({
*/
addElementRules: function(element, inherited, options, rules)
{
if (!element.style) {
return;
}
let elementStyle = this._styleRef(element);
if (!inherited || this._hasInheritedProps(element.style)) {

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

@ -617,7 +617,7 @@ CssLogic.prototype = {
// Add element.style information.
if (element.style.length > 0) {
if (element.style && element.style.length > 0) {
let rule = new CssRule(null, { style: element.style }, element);
rule._matchId = this._matchId;
rule._passId = this._passId;

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

@ -108,6 +108,34 @@ function err(msg, error = null) {
return log(msg, "ERROR: ", error);
}
// Utility function designed to get the current state of execution
// of a blocker.
// We are a little paranoid here to ensure that in case of evaluation
// error we do not block the AsyncShutdown.
function safeGetState(state) {
if (!state) {
return "(none)";
}
try {
// Evaluate state(), normalize the result into something that we can
// safely stringify or upload.
let string = JSON.stringify(state());
let data = JSON.parse(string);
// Simplify the rest of the code by ensuring that we can simply
// concatenate the result to a message.
data.toString = function() {
return string;
};
return data;
} catch (ex) {
try {
return "Error getting state: " + ex;
} catch (ex2) {
return "Could not display error";
}
}
}
/**
* Countdown for a given duration, skipping beats if the computer is too busy,
* sleeping or otherwise unavailable.
@ -186,6 +214,10 @@ function getPhase(topic) {
* resulting promise is either resolved or rejected. If
* |condition| is not a function but another value |v|, it behaves
* as if it were a function returning |v|.
* @param {function*} state Optionally, a function returning
* information about the current state of the blocker as an
* object. Used for providing more details when logging errors or
* crashing.
*
* Examples:
* AsyncShutdown.profileBeforeChange.addBlocker("Module: just a promise",
@ -209,11 +241,14 @@ function getPhase(topic) {
* });
*
*/
addBlocker: function(name, condition) {
addBlocker: function(name, condition, state = null) {
if (typeof name != "string") {
throw new TypeError("Expected a human-readable name as first argument");
}
spinner.addBlocker({name: name, condition: condition});
if (state && typeof state != "function") {
throw new TypeError("Expected nothing or a function as third argument");
}
spinner.addBlocker({name: name, condition: condition, state: state});
}
});
gPhases.set(topic, phase);
@ -274,7 +309,7 @@ Spinner.prototype = {
// are not satisfied yet.
let allMonitors = [];
for (let {condition, name} of conditions) {
for (let {condition, name, state} of conditions) {
// Gather all completion conditions
try {
@ -303,13 +338,15 @@ Spinner.prototype = {
let msg = "A phase completion condition is" +
" taking too long to complete." +
" Condition: " + monitor.name +
" Phase: " + topic;
" Phase: " + topic +
" State: " + safeGetState(state);
warn(msg);
}, DELAY_WARNING_MS, Ci.nsITimer.TYPE_ONE_SHOT);
let monitor = {
isFrozen: true,
name: name
name: name,
state: state
};
condition = condition.then(function onSuccess() {
timer.cancel(); // As a side-effect, this prevents |timer| from
@ -320,7 +357,8 @@ Spinner.prototype = {
let msg = "A completion condition encountered an error" +
" while we were spinning the event loop." +
" Condition: " + name +
" Phase: " + topic;
" Phase: " + topic +
" State: " + safeGetState(state);
warn(msg, error);
monitor.isFrozen = false;
});
@ -331,7 +369,8 @@ Spinner.prototype = {
let msg = "A completion condition encountered an error" +
" while we were initializing the phase." +
" Condition: " + name +
" Phase: " + topic;
" Phase: " + topic +
" State: " + safeGetState(state);
warn(msg, error);
}
@ -362,9 +401,10 @@ Spinner.prototype = {
function onTimeout() {
// Report the problem as best as we can, then crash.
let frozen = [];
for (let {name, isFrozen} of allMonitors) {
let states = [];
for (let {name, isFrozen, state} of allMonitors) {
if (isFrozen) {
frozen.push(name);
frozen.push({name: name, state: safeGetState(state)});
}
}
@ -372,7 +412,7 @@ Spinner.prototype = {
" within a reasonable amount of time. Causing a crash to" +
" ensure that we do not leave the user with an unresponsive" +
" process draining resources." +
" Conditions: " + frozen.join(", ") +
" Conditions: " + JSON.stringify(frozen) +
" Phase: " + topic;
err(msg);
if (gCrashReporter && gCrashReporter.enabled) {

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

@ -54,23 +54,37 @@ add_task(function test_simple_async() {
for (let arg of [undefined, null, "foo", 100, new Error("BOOM")]) {
for (let resolution of [arg, Promise.reject(arg)]) {
for (let success of [false, true]) {
// Asynchronous phase
do_print("Asynchronous test with " + arg + ", " + resolution);
let topic = getUniqueTopic();
let outParam = { isFinished: false };
AsyncShutdown._getPhase(topic).addBlocker(
"Async test",
function() {
if (success) {
return longRunningAsyncTask(resolution, outParam);
} else {
throw resolution;
}
}
);
do_check_false(outParam.isFinished);
Services.obs.notifyObservers(null, topic, null);
do_check_eq(outParam.isFinished, success);
for (let state of [[null],
[],
[() => "some state"],
[function() {
throw new Error("State BOOM"); }],
[function() {
return {
toJSON: function() {
throw new Error("State.toJSON BOOM");
}
};
}]]) {
// Asynchronous phase
do_print("Asynchronous test with " + arg + ", " + resolution);
let topic = getUniqueTopic();
let outParam = { isFinished: false };
AsyncShutdown._getPhase(topic).addBlocker(
"Async test",
function() {
if (success) {
return longRunningAsyncTask(resolution, outParam);
} else {
throw resolution;
}
},
...state
);
do_check_false(outParam.isFinished);
Services.obs.notifyObservers(null, topic, null);
do_check_eq(outParam.isFinished, success);
}
}
// Synchronous phase - just test that we don't throw/freeze
@ -128,6 +142,9 @@ add_task(function test_various_failures() {
exn = get_exn(() => phase.addBlocker(null, true));
do_check_eq(exn.name, "TypeError");
exn = get_exn(() => phase.addBlocker("Test 2", () => true, "not a function"));
do_check_eq(exn.name, "TypeError");
});
add_task(function() {

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

@ -96,6 +96,9 @@ const INTEGER_KEY_MAP = {
daily_users: "dailyUsers"
};
// Wrap the XHR factory so that tests can override with a mock
let XHRequest = Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1",
"nsIXMLHttpRequest");
function convertHTMLToPlainText(html) {
if (!html)
@ -602,12 +605,15 @@ this.AddonRepository = {
* The array of add-on ids to repopulate the cache with
* @param aCallback
* The optional callback to call once complete
* @param aTimeout
* (Optional) timeout in milliseconds to abandon the XHR request
* if we have not received a response from the server.
*/
repopulateCache: function AddonRepo_repopulateCache(aIds, aCallback) {
this._repopulateCacheInternal(aIds, aCallback, false);
repopulateCache: function(aIds, aCallback, aTimeout) {
this._repopulateCacheInternal(aIds, aCallback, false, aTimeout);
},
_repopulateCacheInternal: function AddonRepo_repopulateCacheInternal(aIds, aCallback, aSendPerformance) {
_repopulateCacheInternal: function(aIds, aCallback, aSendPerformance, aTimeout) {
// Completely remove cache if caching is not enabled
if (!this.cacheEnabled) {
this._addons = null;
@ -637,7 +643,7 @@ this.AddonRepository = {
if (aCallback)
aCallback();
}
}, aSendPerformance);
}, aSendPerformance, aTimeout);
});
},
@ -758,8 +764,11 @@ this.AddonRepository = {
* @param aSendPerformance
* Boolean indicating whether to send performance data with the
* request.
* @param aTimeout
* (Optional) timeout in milliseconds to abandon the XHR request
* if we have not received a response from the server.
*/
_beginGetAddons: function AddonRepo_beginGetAddons(aIDs, aCallback, aSendPerformance) {
_beginGetAddons: function(aIDs, aCallback, aSendPerformance, aTimeout) {
let ids = aIDs.slice(0);
let params = {
@ -841,7 +850,7 @@ this.AddonRepository = {
self._reportSuccess(results, -1);
}
this._beginSearch(url, ids.length, aCallback, handleResults);
this._beginSearch(url, ids.length, aCallback, handleResults, aTimeout);
},
/**
@ -1388,7 +1397,7 @@ this.AddonRepository = {
},
// Begins a new search if one isn't currently executing
_beginSearch: function AddonRepo_beginSearch(aURI, aMaxResults, aCallback, aHandleResults) {
_beginSearch: function(aURI, aMaxResults, aCallback, aHandleResults, aTimeout) {
if (this._searching || aURI == null || aMaxResults <= 0) {
aCallback.searchFailed();
return;
@ -1400,23 +1409,23 @@ this.AddonRepository = {
LOG("Requesting " + aURI);
this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
createInstance(Ci.nsIXMLHttpRequest);
this._request = new XHRequest();
this._request.mozBackgroundRequest = true;
this._request.open("GET", aURI, true);
this._request.overrideMimeType("text/xml");
if (aTimeout) {
this._request.timeout = aTimeout;
}
let self = this;
this._request.addEventListener("error", function beginSearch_errorListener(aEvent) {
self._reportFailure();
}, false);
this._request.addEventListener("load", function beginSearch_loadListener(aEvent) {
this._request.addEventListener("error", aEvent => this._reportFailure(), false);
this._request.addEventListener("timeout", aEvent => this._reportFailure(), false);
this._request.addEventListener("load", aEvent => {
let request = aEvent.target;
let responseXML = request.responseXML;
if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
(request.status != 200 && request.status != 0)) {
self._reportFailure();
this._reportFailure();
return;
}
@ -1429,7 +1438,7 @@ this.AddonRepository = {
totalResults = parsedTotalResults;
let compatElements = documentElement.getElementsByTagName("addon_compatibility");
let compatData = self._parseAddonCompatData(compatElements);
let compatData = this._parseAddonCompatData(compatElements);
aHandleResults(elements, totalResults, compatData);
}, false);

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

@ -394,10 +394,6 @@ function UpdateParser(aId, aUpdateKey, aUrl, aObserver) {
this.observer = aObserver;
this.url = aUrl;
this.timer = Cc["@mozilla.org/timer;1"].
createInstance(Ci.nsITimer);
this.timer.initWithCallback(this, TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
let requireBuiltIn = true;
try {
requireBuiltIn = Services.prefs.getBoolPref(PREF_UPDATE_REQUIREBUILTINCERTS);
@ -415,9 +411,11 @@ function UpdateParser(aId, aUpdateKey, aUrl, aObserver) {
// Prevent the request from writing to cache.
this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
this.request.overrideMimeType("text/xml");
this.request.timeout = TIMEOUT;
var self = this;
this.request.addEventListener("load", function loadEventListener(event) { self.onLoad() }, false);
this.request.addEventListener("error", function errorEventListener(event) { self.onError() }, false);
this.request.addEventListener("timeout", function timeoutEventListener(event) { self.onTimeout() }, false);
this.request.send(null);
}
catch (e) {
@ -430,15 +428,12 @@ UpdateParser.prototype = {
updateKey: null,
observer: null,
request: null,
timer: null,
url: null,
/**
* Called when the manifest has been successfully loaded.
*/
onLoad: function UP_onLoad() {
this.timer.cancel();
this.timer = null;
let request = this.request;
this.request = null;
@ -505,13 +500,19 @@ UpdateParser.prototype = {
this.notifyError(AddonUpdateChecker.ERROR_UNKNOWN_FORMAT);
},
/**
* Called when the request times out
*/
onTimeout: function() {
this.request = null;
WARN("Request for " + this.url + " timed out");
this.notifyError(AddonUpdateChecker.ERROR_TIMEOUT);
},
/**
* Called when the manifest failed to load.
*/
onError: function UP_onError() {
this.timer.cancel();
this.timer = null;
if (!Components.isSuccessCode(this.request.status)) {
WARN("Request failed: " + this.url + " - " + this.request.status);
}
@ -550,25 +551,10 @@ UpdateParser.prototype = {
}
},
/**
* Called when the request has timed out and should be canceled.
*/
notify: function UP_notify(aTimer) {
this.timer = null;
this.request.abort();
this.request = null;
WARN("Request timed out");
this.notifyError(AddonUpdateChecker.ERROR_TIMEOUT);
},
/**
* Called to cancel an in-progress update check.
*/
cancel: function UP_cancel() {
this.timer.cancel();
this.timer = null;
this.request.abort();
this.request = null;
this.notifyError(AddonUpdateChecker.ERROR_CANCELLED);

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

@ -12,6 +12,9 @@ const PREF_UPDATE_EXTENSIONS_ENABLED = "extensions.update.enabled";
const PREF_XPINSTALL_ENABLED = "xpinstall.enabled";
const PREF_EM_HOTFIX_ID = "extensions.hotfix.id";
// timeout (in milliseconds) to wait for response to the metadata ping
const METADATA_TIMEOUT = 30000;
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/AddonManager.jsm");
Components.utils.import("resource://gre/modules/AddonRepository.jsm");
@ -175,7 +178,7 @@ var gVersionInfoPage = {
for (let addon of gUpdateWizard.addons)
addon.findUpdates(gVersionInfoPage, AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED);
});
});
}, METADATA_TIMEOUT);
});
},

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

@ -38,6 +38,7 @@ MOCHITEST_BROWSER_MAIN_FILES = \
browser_discovery.js \
browser_dragdrop.js \
browser_list.js \
browser_metadataTimeout.js \
browser_searching.js \
browser_sorting.js \
browser_uninstalling.js \

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

@ -0,0 +1,113 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Test how update window behaves when metadata ping times out
// bug 965788
const URI_EXTENSION_UPDATE_DIALOG = "chrome://mozapps/content/extensions/update.xul";
const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url";
const PREF_MIN_PLATFORM_COMPAT = "extensions.minCompatiblePlatformVersion";
Components.utils.import("resource://gre/modules/Promise.jsm");
let repo = {};
let ARContext = Components.utils.import("resource://gre/modules/AddonRepository.jsm", repo);
info("ARContext: " + Object.keys(ARContext).join(", "));
// Mock out the XMLHttpRequest factory for AddonRepository so
// we can reply with a timeout
let pXHRStarted = Promise.defer();
let oldXHRConstructor = ARContext.XHRequest;
ARContext.XHRequest = function() {
this._handlers = new Map();
this.mozBackgroundRequest = false;
this.timeout = undefined;
this.open = function(aMethod, aURI, aAsync) {
this.method = aMethod;
this.uri = aURI;
this.async = aAsync;
info("Opened XHR for " + aMethod + " " + aURI);
};
this.overrideMimeType = function(aMimeType) {
this.mimeType = aMimeType;
};
this.addEventListener = function(aEvent, aHandler, aCapture) {
this._handlers.set(aEvent, aHandler);
};
this.send = function(aBody) {
info("Send XHR for " + this.method + " " + this.uri + " handlers: " + [this._handlers.keys()].join(", "));
pXHRStarted.resolve(this);
}
};
// Returns promise{window}, resolves with a handle to the compatibility
// check window
function promise_open_compatibility_window(aInactiveAddonIds) {
let deferred = Promise.defer();
// This will reset the longer timeout multiplier to 2 which will give each
// test that calls open_compatibility_window a minimum of 60 seconds to
// complete.
requestLongerTimeout(100 /* XXX was 2 */);
var variant = Cc["@mozilla.org/variant;1"].
createInstance(Ci.nsIWritableVariant);
variant.setFromVariant(aInactiveAddonIds);
// Cannot be modal as we want to interract with it, shouldn't cause problems
// with testing though.
var features = "chrome,centerscreen,dialog,titlebar";
var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
getService(Ci.nsIWindowWatcher);
var win = ww.openWindow(null, URI_EXTENSION_UPDATE_DIALOG, "", features, variant);
win.addEventListener("load", function() {
function page_shown(aEvent) {
if (aEvent.target.pageid)
info("Page " + aEvent.target.pageid + " shown");
}
win.removeEventListener("load", arguments.callee, false);
info("Compatibility dialog opened");
win.addEventListener("pageshow", page_shown, false);
win.addEventListener("unload", function() {
win.removeEventListener("unload", arguments.callee, false);
win.removeEventListener("pageshow", page_shown, false);
dump("Compatibility dialog closed\n");
}, false);
deferred.resolve(win);
}, false);
return deferred.promise;
}
function promise_window_close(aWindow) {
let deferred = Promise.defer();
aWindow.addEventListener("unload", function() {
aWindow.removeEventListener("unload", arguments.callee, false);
deferred.resolve(aWindow);
}, false);
return deferred.promise;
}
// Start the compatibility update dialog, but use the mock XHR to respond with
// a timeout
add_task(function* amo_ping_timeout() {
Services.prefs.setBoolPref(PREF_GETADDONS_CACHE_ENABLED, true);
let compatWindow = yield promise_open_compatibility_window([]);
let xhr = yield pXHRStarted.promise;
is(xhr.timeout, 30000, "XHR request should have 30 second timeout");
ok(xhr._handlers.has("timeout"), "Timeout handler set on XHR");
// call back the timeout handler
xhr._handlers.get("timeout")();
// Put the old XHR constructor back
ARContext.XHRequest = oldXHRConstructor;
// The window should close without further interaction
yield promise_window_close(compatWindow);
});

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

@ -23,6 +23,11 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");
Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/Promise.jsm");
Components.utils.import("resource://gre/modules/Task.jsm");
Components.utils.import("resource://gre/modules/osfile.jsm");
Services.prefs.setBoolPref("toolkit.osfile.log", true);
// We need some internal bits of AddonManager
let AMscope = Components.utils.import("resource://gre/modules/AddonManager.jsm");
@ -444,6 +449,7 @@ function shutdownManager() {
// This would be cleaner if I could get it as the rejection reason from
// the AddonManagerInternal.shutdown() promise
gXPISaveError = XPIscope.XPIProvider._shutdownError;
do_print("gXPISaveError set to: " + gXPISaveError);
AddonManagerPrivate.unregisterProvider(XPIscope.XPIProvider);
Components.utils.unload("resource://gre/modules/XPIProvider.jsm");
}
@ -691,6 +697,8 @@ function writeInstallRDFForExtension(aData, aDir, aId, aExtraFile) {
*
* @param aExt a file pointing to either the packed extension or its unpacked directory.
* @param aTime the time to which we set the lastModifiedTime of the extension
*
* @deprecated Please use promiseSetExtensionModifiedTime instead
*/
function setExtensionModifiedTime(aExt, aTime) {
aExt.lastModifiedTime = aTime;
@ -702,6 +710,25 @@ function setExtensionModifiedTime(aExt, aTime) {
entries.close();
}
}
function promiseSetExtensionModifiedTime(aPath, aTime) {
return Task.spawn(function* () {
yield OS.File.setDates(aPath, aTime, aTime);
let entries, iterator;
try {
let iterator = new OS.File.DirectoryIterator(aPath);
entries = yield iterator.nextBatch();
} catch (ex if ex instanceof OS.File.Error) {
return;
} finally {
if (iterator) {
iterator.close();
}
}
for (let entry of entries) {
yield promiseSetExtensionModifiedTime(entry.path, aTime);
}
});
}
/**
* Manually installs an XPI file into an install location by either copying the
@ -1082,7 +1109,11 @@ function completeAllInstalls(aInstalls, aCallback) {
function installAllFiles(aFiles, aCallback, aIgnoreIncompatible) {
let count = aFiles.length;
let installs = [];
function callback() {
if (aCallback) {
aCallback();
}
}
aFiles.forEach(function(aFile) {
AddonManager.getInstallForFile(aFile, function(aInstall) {
if (!aInstall)
@ -1093,11 +1124,18 @@ function installAllFiles(aFiles, aCallback, aIgnoreIncompatible) {
installs.push(aInstall);
if (--count == 0)
completeAllInstalls(installs, aCallback);
completeAllInstalls(installs, callback);
});
});
}
function promiseInstallAllFiles(aFiles, aIgnoreIncompatible) {
let deferred = Promise.defer();
installAllFiles(aFiles, deferred.resolve, aIgnoreIncompatible);
return deferred.promise;
}
if ("nsIWindowsRegKey" in AM_Ci) {
var MockRegistry = {
LOCAL_MACHINE: {},
@ -1473,3 +1511,17 @@ function callback_soon(aFunction) {
}, aFunction.name ? "delayed callback " + aFunction.name : "delayed callback");
}
}
/**
* A promise-based variant of AddonManager.getAddonsByIDs.
*
* @param {array} list As the first argument of AddonManager.getAddonsByIDs
* @return {promise}
* @resolve {array} The list of add-ons sent by AddonManaget.getAddonsByIDs to
* its callback.
*/
function promiseAddonsByIDs(list) {
let deferred = Promise.defer();
AddonManager.getAddonsByIDs(list, deferred.resolve);
return deferred.promise;
}

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

@ -415,7 +415,20 @@ function run_test() {
}
function end_test() {
gServer.stop(do_test_finished);
let testDir = gProfD.clone();
testDir.append("extensions");
testDir.append("staged");
gServer.stop(function() {
function loop() {
if (!testDir.exists()) {
do_print("Staged directory has been cleaned up");
do_test_finished();
}
do_print("Waiting 1 second until cleanup is complete");
do_timeout(1000, loop);
}
loop();
});
}
// Tests homepageURL, getRecommendedURL() and getSearchURL()

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

@ -134,8 +134,7 @@ var theme2 = {
const profileDir = gProfD.clone();
profileDir.append("extensions");
function run_test() {
do_test_pending();
add_task(function* init() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2");
writeInstallRDFForExtension(addon1, profileDir);
@ -154,37 +153,221 @@ function run_test() {
// New profile so new add-ons are ignored
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
AddonManager.getAddonsByIDs(["addon2@tests.mozilla.org",
let [a2, a3, a4, a7, t2] =
yield promiseAddonsByIDs(["addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme2@tests.mozilla.org"], function([a2, a3, a4,
a7, t2]) {
// Set up the initial state
a2.userDisabled = true;
a4.userDisabled = true;
a7.userDisabled = true;
t2.userDisabled = false;
a3.findUpdates({
onUpdateFinished: function() {
a4.findUpdates({
onUpdateFinished: function() {
// Let the updates finish before restarting the manager
do_execute_soon(run_test_1);
}
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
}
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
});
}
"theme2@tests.mozilla.org"]);
let deferredUpdateFinished = Promise.defer();
// Set up the initial state
a2.userDisabled = true;
a4.userDisabled = true;
a7.userDisabled = true;
t2.userDisabled = false;
a3.findUpdates({
onUpdateFinished: function() {
a4.findUpdates({
onUpdateFinished: function() {
// Let the updates finish before restarting the manager
deferredUpdateFinished.resolve();
}
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
}
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
function end_test() {
testserver.stop(do_test_finished);
}
yield deferredUpdateFinished.promise;
});
function run_test_1() {
add_task(function* run_test_1() {
restartManager();
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
let [a1, a2, a3, a4, a5, a6, a7, t1, t2] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"]);
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_false(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_true(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a5, null);
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
// Open another handle on the JSON DB with as much Unix and Windows locking
// as we can to simulate some other process interfering with it
shutdownManager();
do_print("Locking " + gExtensionsJSON.path);
let options = {
winShare: 0
};
if (OS.Constants.libc.O_EXLOCK)
options.unixFlags = OS.Constants.libc.O_EXLOCK;
let file = yield OS.File.open(gExtensionsJSON.path, {read:true, write:true, existing:true}, options);
let filePermissions = gExtensionsJSON.permissions;
if (!OS.Constants.Win) {
gExtensionsJSON.permissions = 0;
}
startupManager(false);
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
// Accessing the add-ons should open and recover the database
[a1, a2, a3, a4, a5, a6, a7, t1, t2] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"]);
// Should be correctly recovered
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
// Should be correctly recovered
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
// The compatibility update won't be recovered but it should still be
// active for this session
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_false(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
// The compatibility update won't be recovered and with strict
// compatibility it would not have been able to tell that it was
// previously userDisabled. However, without strict compat, it wasn't
// appDisabled, so it knows it must have been userDisabled.
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_true(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a5, null);
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
// Should be correctly recovered
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
// Should be correctly recovered
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
// Restarting will actually apply changes to extensions.ini which will
// then be put into the in-memory database when we next fail to load the
// real thing
restartManager();
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
[a1, a2, a3, a4, a5, a6, a7, t1, t2] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
@ -192,340 +375,155 @@ function run_test_1() {
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"],
callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) {
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
"theme2@tests.mozilla.org"]);
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_false(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_true(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_false(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
do_check_neq(a5, null);
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_true(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a5, null);
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
// Open another handle on the JSON DB with as much Unix and Windows locking
// as we can to simulate some other process interfering with it
shutdownManager();
do_print("Locking " + gExtensionsJSON.path);
let options = {
winShare: 0
};
if (OS.Constants.libc.O_EXLOCK)
options.unixFlags = OS.Constants.libc.O_EXLOCK;
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
OS.File.open(gExtensionsJSON.path, {read:true, write:true, existing:true}, options).then(
file => {
filePermissions = gExtensionsJSON.permissions;
if (!OS.Constants.Win) {
gExtensionsJSON.permissions = 0;
}
startupManager(false);
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
// Accessing the add-ons should open and recover the database
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"],
callback_soon(function get_after_lock([a1, a2, a3, a4, a5, a6, a7, t1, t2]) {
// Should be correctly recovered
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
// Should be correctly recovered
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
// The compatibility update won't be recovered but it should still be
// active for this session
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_false(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
// The compatibility update won't be recovered and with strict
// compatibility it would not have been able to tell that it was
// previously userDisabled. However, without strict compat, it wasn't
// appDisabled, so it knows it must have been userDisabled.
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_true(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a5, null);
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
// Should be correctly recovered
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
// Should be correctly recovered
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
// Restarting will actually apply changes to extensions.ini which will
// then be put into the in-memory database when we next fail to load the
// real thing
restartManager();
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"],
callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) {
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_false(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_true(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a5, null);
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
// After allowing access to the original DB things should go back to as
// they were previously
shutdownManager();
do_print("Unlocking " + gExtensionsJSON.path);
file.close();
gExtensionsJSON.permissions = filePermissions;
startupManager();
// After allowing access to the original DB things should go back to as
// they were previously
shutdownManager();
do_print("Unlocking " + gExtensionsJSON.path);
yield file.close();
gExtensionsJSON.permissions = filePermissions;
startupManager();
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"],
callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) {
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
[a1, a2, a3, a4, a5, a6, a7, t1, t2] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"]);
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_false(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_true(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_false(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
do_check_neq(a5, null);
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_true(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a5, null);
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
end_test();
}));
}));
}));
},
do_report_unexpected_exception
);
}));
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
});
function run_test() {
run_next_test();
}

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

@ -71,8 +71,8 @@ var addon5 = {
const profileDir = gProfD.clone();
profileDir.append("extensions");
function run_test() {
do_test_pending();
add_task(function() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2");
writeInstallRDFForExtension(addon1, profileDir);
@ -83,7 +83,8 @@ function run_test() {
// Make it look like add-on 5 was installed some time in the past so the update is
// detected
setExtensionModifiedTime(getFileForAddon(profileDir, addon5.id), Date.now() - (60000));
let path = getFileForAddon(profileDir, addon5.id).path;
yield promiseSetExtensionModifiedTime(path, Date.now() - (60000));
// Startup the profile and setup the initial state
startupManager();
@ -91,205 +92,201 @@ function run_test() {
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
AddonManager.getAddonByID("addon2@tests.mozilla.org", callback_soon(function(a2) {
a2.userDisabled = true;
let a1, a2, a3, a4, a5, a6;
restartManager();
[a2] = yield promiseAddonsByIDs(["addon2@tests.mozilla.org"]);
a2.userDisabled = true;
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org"],
function([a1, a2, a3, a4, a5]) {
a2.userDisabled = false;
a3.userDisabled = true;
a4.uninstall();
restartManager();
installAllFiles([do_get_addon("test_locked2_5"),
do_get_addon("test_locked2_6")], function locked_installed() {
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
[a1, a2, a3, a4, a5] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org"]);
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_false(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_ENABLE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
a2.userDisabled = false;
a3.userDisabled = true;
a4.uninstall();
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_true(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_DISABLE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
yield promiseInstallAllFiles([do_get_addon("test_locked2_5"),
do_get_addon("test_locked2_6")]);
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_neq(a4, null);
do_check_true(a4.isActive);
do_check_false(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_UNINSTALL);
do_check_true(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_false(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_ENABLE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a5, null);
do_check_eq(a5.version, "1.0");
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_UPGRADE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_true(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_DISABLE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
// Open another handle on the JSON DB with as much Unix and Windows locking
// as we can to simulate some other process interfering with it
shutdownManager();
do_print("Locking " + gExtensionsJSON.path);
let options = {
winShare: 0
};
if (OS.Constants.libc.O_EXLOCK)
options.unixFlags = OS.Constants.libc.O_EXLOCK;
do_check_neq(a4, null);
do_check_true(a4.isActive);
do_check_false(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_UNINSTALL);
do_check_true(isExtensionInAddonsList(profileDir, a4.id));
OS.File.open(gExtensionsJSON.path, {read:true, write:true, existing:true}, options).then(
file => {
filePermissions = gExtensionsJSON.permissions;
if (!OS.Constants.Win) {
gExtensionsJSON.permissions = 0;
}
startupManager(false);
do_check_neq(a5, null);
do_check_eq(a5.version, "1.0");
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_UPGRADE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
// Open another handle on the JSON DB with as much Unix and Windows locking
// as we can to simulate some other process interfering with it
shutdownManager();
do_print("Locking " + gExtensionsJSON.path);
let options = {
winShare: 0
};
if (OS.Constants.libc.O_EXLOCK)
options.unixFlags = OS.Constants.libc.O_EXLOCK;
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org"],
callback_soon(function([a1, a2, a3, a4, a5, a6]) {
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
let file = yield OS.File.open(gExtensionsJSON.path, {read:true, write:true, existing:true}, options);
do_check_neq(a2, null);
do_check_true(a2.isActive);
do_check_false(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a2.id));
let filePermissions = gExtensionsJSON.permissions;
if (!OS.Constants.Win) {
gExtensionsJSON.permissions = 0;
}
startupManager(false);
do_check_neq(a3, null);
do_check_false(a3.isActive);
do_check_true(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a3.id));
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
do_check_eq(a4, null);
[a1, a2, a3, a4, a5, a6] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org"]);
do_check_neq(a5, null);
do_check_eq(a5.version, "2.0");
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a6.id));
do_check_neq(a2, null);
do_check_true(a2.isActive);
do_check_false(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a2.id));
// After allowing access to the original DB things should still be
// back how they were before the lock
shutdownManager();
file.close();
gExtensionsJSON.permissions = filePermissions;
startupManager();
do_check_neq(a3, null);
do_check_false(a3.isActive);
do_check_true(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a3.id));
// On Unix, we can save the DB even when the original file wasn't
// readable, so our changes were saved. On Windows,
// these things happened when we had no access to the database so
// they are seen as external changes when we get the database back
if (gXPISaveError) {
do_print("Previous XPI save failed");
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED,
["addon6@tests.mozilla.org"]);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED,
["addon4@tests.mozilla.org"]);
}
else {
do_print("Previous XPI save succeeded");
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
}
do_check_eq(a4, null);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org"],
function([a1, a2, a3, a4, a5, a6]) {
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_neq(a5, null);
do_check_eq(a5.version, "2.0");
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a2, null);
do_check_true(a2.isActive);
do_check_false(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a6.id));
do_check_neq(a3, null);
do_check_false(a3.isActive);
do_check_true(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a3.id));
// After allowing access to the original DB things should still be
// back how they were before the lock
shutdownManager();
yield file.close();
gExtensionsJSON.permissions = filePermissions;
startupManager();
do_check_eq(a4, null);
// On Unix, we can save the DB even when the original file wasn't
// readable, so our changes were saved. On Windows,
// these things happened when we had no access to the database so
// they are seen as external changes when we get the database back
if (gXPISaveError) {
do_print("Previous XPI save failed");
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED,
["addon6@tests.mozilla.org"]);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED,
["addon4@tests.mozilla.org"]);
}
else {
do_print("Previous XPI save succeeded");
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
check_startup_changes(AddonManager.STARTUP_CHANGE_UNINSTALLED, []);
}
do_check_neq(a5, null);
do_check_eq(a5.version, "2.0");
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
[a1, a2, a3, a4, a5, a6] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org"]);
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a6.id));
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
end_test();
});
}));
},
do_report_unexpected_exception
);
});
});
}));
do_check_neq(a2, null);
do_check_true(a2.isActive);
do_check_false(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a3, null);
do_check_false(a3.isActive);
do_check_true(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a3.id));
do_check_eq(a4, null);
do_check_neq(a5, null);
do_check_eq(a5.version, "2.0");
do_check_true(a5.isActive);
do_check_false(a5.userDisabled);
do_check_false(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a6.id));
});
function run_test() {
run_next_test();
}
function end_test() {
do_execute_soon(do_test_finished);
}

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

@ -134,8 +134,7 @@ var theme2 = {
const profileDir = gProfD.clone();
profileDir.append("extensions");
function run_test() {
do_test_pending();
add_task(function* init() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2");
writeInstallRDFForExtension(addon1, profileDir);
@ -154,37 +153,222 @@ function run_test() {
// New profile so new add-ons are ignored
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
AddonManager.getAddonsByIDs(["addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme2@tests.mozilla.org"], function([a2, a3, a4,
a7, t2]) {
// Set up the initial state
a2.userDisabled = true;
a4.userDisabled = true;
a7.userDisabled = true;
t2.userDisabled = false;
a3.findUpdates({
onUpdateFinished: function() {
a4.findUpdates({
onUpdateFinished: function() {
do_execute_soon(run_test_1);
}
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
}
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
});
}
let a1, a2, a3, a4, a5, a6, a7, t1, t2;
function end_test() {
testserver.stop(do_test_finished);
}
[a2, a3, a4, a7, t2] =
yield promiseAddonsByIDs(["addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme2@tests.mozilla.org"]);
function run_test_1() {
// Set up the initial state
let deferredUpdateFinished = Promise.defer();
a2.userDisabled = true;
a4.userDisabled = true;
a7.userDisabled = true;
t2.userDisabled = false;
a3.findUpdates({
onUpdateFinished: function() {
a4.findUpdates({
onUpdateFinished: function() {
deferredUpdateFinished.resolve();
}
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
}
}, AddonManager.UPDATE_WHEN_PERIODIC_UPDATE);
yield deferredUpdateFinished.promise;
});
add_task(function* run_test_1() {
let a1, a2, a3, a4, a5, a6, a7, t1, t2;
restartManager();
[a1, a2, a3, a4, a5, a6, a7, t1, t2] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"]);
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_false(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_true(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a5, null);
do_check_false(a5.isActive);
do_check_false(a5.userDisabled);
do_check_true(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
// Open another handle on the JSON DB with as much Unix and Windows locking
// as we can to simulate some other process interfering with it
shutdownManager();
do_print("Locking " + gExtensionsJSON.path);
let options = {
winShare: 0
};
if (OS.Constants.libc.O_EXLOCK)
options.unixFlags = OS.Constants.libc.O_EXLOCK;
let file = yield OS.File.open(gExtensionsJSON.path, {read:true, write:true, existing:true}, options);
let filePermissions = gExtensionsJSON.permissions;
if (!OS.Constants.Win) {
gExtensionsJSON.permissions = 0;
}
startupManager(false);
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
// Accessing the add-ons should open and recover the database
[a1, a2, a3, a4, a5, a6, a7, t1, t2] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"]);
// Should be correctly recovered
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
// Should be correctly recovered
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
// The compatibility update won't be recovered but it should still be
// active for this session
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_false(a3.userDisabled);
do_check_true(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_DISABLE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
// The compatibility update won't be recovered and it will not have been
// able to tell that it was previously userDisabled
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_false(a4.userDisabled);
do_check_true(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a5, null);
do_check_false(a5.isActive);
do_check_false(a5.userDisabled);
do_check_true(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
// Should be correctly recovered
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
// Should be correctly recovered
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
// Restarting will actually apply changes to extensions.ini which will
// then be put into the in-memory database when we next fail to load the
// real thing
restartManager();
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
[a1, a2, a3, a4, a5, a6, a7, t1, t2] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
@ -192,359 +376,176 @@ function run_test_1() {
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"],
callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) {
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
"theme2@tests.mozilla.org"]);
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_neq(a3, null);
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a3, null);
do_check_false(a3.isActive);
do_check_false(a3.userDisabled);
do_check_true(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a3.id));
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_false(a4.userDisabled);
do_check_true(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a5, null);
do_check_false(a5.isActive);
do_check_false(a5.userDisabled);
do_check_true(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
// After allowing access to the original DB things should go back to as
// back how they were before the lock
shutdownManager();
do_print("Unlocking " + gExtensionsJSON.path);
yield file.close();
gExtensionsJSON.permissions = filePermissions;
startupManager(false);
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
[a1, a2, a3, a4, a5, a6, a7, t1, t2] =
yield promiseAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"]);
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a3, null);
do_check_false(a3.userDisabled);
// On Unix, we may be able to save our changes over the locked DB so we
// remember that this extension was changed to disabled. On Windows we
// couldn't replace the old DB so we read the older version of the DB
// where the extension is enabled
if (gXPISaveError) {
do_print("XPI save failed");
do_check_true(a3.isActive);
do_check_false(a3.userDisabled);
do_check_false(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
}
else {
do_print("XPI save succeeded");
do_check_false(a3.isActive);
do_check_true(a3.appDisabled);
do_check_false(isExtensionInAddonsList(profileDir, a3.id));
}
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_neq(a4, null);
do_check_false(a4.isActive);
// The reverse of the platform difference for a3 - Unix should
// stay the same as the last iteration because the save succeeded,
// Windows should still say userDisabled
if (OS.Constants.Win) {
do_check_true(a4.userDisabled);
do_check_false(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
}
else {
do_check_false(a4.userDisabled);
do_check_true(a4.appDisabled);
}
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a5, null);
do_check_false(a5.isActive);
do_check_false(a5.userDisabled);
do_check_true(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a5, null);
do_check_false(a5.isActive);
do_check_false(a5.userDisabled);
do_check_true(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
});
// Open another handle on the JSON DB with as much Unix and Windows locking
// as we can to simulate some other process interfering with it
shutdownManager();
do_print("Locking " + gExtensionsJSON.path);
let options = {
winShare: 0
};
if (OS.Constants.libc.O_EXLOCK)
options.unixFlags = OS.Constants.libc.O_EXLOCK;
OS.File.open(gExtensionsJSON.path, {read:true, write:true, existing:true}, options).then(
file => {
filePermissions = gExtensionsJSON.permissions;
if (!OS.Constants.Win) {
gExtensionsJSON.permissions = 0;
}
startupManager(false);
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
// Accessing the add-ons should open and recover the database
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"],
callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) {
// Should be correctly recovered
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
// Should be correctly recovered
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
// The compatibility update won't be recovered but it should still be
// active for this session
do_check_neq(a3, null);
do_check_true(a3.isActive);
do_check_false(a3.userDisabled);
do_check_true(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_DISABLE);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
// The compatibility update won't be recovered and it will not have been
// able to tell that it was previously userDisabled
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_false(a4.userDisabled);
do_check_true(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a5, null);
do_check_false(a5.isActive);
do_check_false(a5.userDisabled);
do_check_true(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
// Should be correctly recovered
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
// Should be correctly recovered
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
// Restarting will actually apply changes to extensions.ini which will
// then be put into the in-memory database when we next fail to load the
// real thing
restartManager();
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"],
callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) {
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a3, null);
do_check_false(a3.isActive);
do_check_false(a3.userDisabled);
do_check_true(a3.appDisabled);
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a3.id));
do_check_neq(a4, null);
do_check_false(a4.isActive);
do_check_false(a4.userDisabled);
do_check_true(a4.appDisabled);
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_neq(a5, null);
do_check_false(a5.isActive);
do_check_false(a5.userDisabled);
do_check_true(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
// After allowing access to the original DB things should go back to as
// back how they were before the lock
shutdownManager();
do_print("Unlocking " + gExtensionsJSON.path);
file.close();
gExtensionsJSON.permissions = filePermissions;
startupManager(false);
// Shouldn't have seen any startup changes
check_startup_changes(AddonManager.STARTUP_CHANGE_INSTALLED, []);
AddonManager.getAddonsByIDs(["addon1@tests.mozilla.org",
"addon2@tests.mozilla.org",
"addon3@tests.mozilla.org",
"addon4@tests.mozilla.org",
"addon5@tests.mozilla.org",
"addon6@tests.mozilla.org",
"addon7@tests.mozilla.org",
"theme1@tests.mozilla.org",
"theme2@tests.mozilla.org"],
callback_soon(function([a1, a2, a3, a4, a5, a6, a7, t1, t2]) {
do_check_neq(a1, null);
do_check_true(a1.isActive);
do_check_false(a1.userDisabled);
do_check_false(a1.appDisabled);
do_check_eq(a1.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isExtensionInAddonsList(profileDir, a1.id));
do_check_neq(a2, null);
do_check_false(a2.isActive);
do_check_true(a2.userDisabled);
do_check_false(a2.appDisabled);
do_check_eq(a2.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a2.id));
do_check_neq(a3, null);
do_check_false(a3.userDisabled);
// On Unix, we may be able to save our changes over the locked DB so we
// remember that this extension was changed to disabled. On Windows we
// couldn't replace the old DB so we read the older version of the DB
// where the extension is enabled
if (gXPISaveError) {
do_print("XPI save failed");
do_check_true(a3.isActive);
do_check_false(a3.appDisabled);
do_check_true(isExtensionInAddonsList(profileDir, a3.id));
}
else {
do_print("XPI save succeeded");
do_check_false(a3.isActive);
do_check_true(a3.appDisabled);
do_check_false(isExtensionInAddonsList(profileDir, a3.id));
}
do_check_eq(a3.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a4, null);
do_check_false(a4.isActive);
// The reverse of the platform difference for a3 - Unix should
// stay the same as the last iteration because the save succeeded,
// Windows should still say userDisabled
if (OS.Constants.Win) {
do_check_true(a4.userDisabled);
do_check_false(a4.appDisabled);
}
else {
do_check_false(a4.userDisabled);
do_check_true(a4.appDisabled);
}
do_check_false(isExtensionInAddonsList(profileDir, a4.id));
do_check_eq(a4.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a5, null);
do_check_false(a5.isActive);
do_check_false(a5.userDisabled);
do_check_true(a5.appDisabled);
do_check_eq(a5.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isExtensionInAddonsList(profileDir, a5.id));
do_check_neq(a6, null);
do_check_true(a6.isActive);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_eq(a6.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(a7, null);
do_check_false(a7.isActive);
do_check_true(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_eq(a7.pendingOperations, AddonManager.PENDING_NONE);
do_check_neq(t1, null);
do_check_false(t1.isActive);
do_check_true(t1.userDisabled);
do_check_false(t1.appDisabled);
do_check_eq(t1.pendingOperations, AddonManager.PENDING_NONE);
do_check_false(isThemeInAddonsList(profileDir, t1.id));
do_check_neq(t2, null);
do_check_true(t2.isActive);
do_check_false(t2.userDisabled);
do_check_false(t2.appDisabled);
do_check_eq(t2.pendingOperations, AddonManager.PENDING_NONE);
do_check_true(isThemeInAddonsList(profileDir, t2.id));
end_test();
}));
}));
}));
},
do_report_unexpected_exception
);
}));
function run_test() {
run_next_test();
}