mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
d89c4cc96e
@ -908,6 +908,11 @@ SourceScripts.prototype = {
|
|||||||
|
|
||||||
this._addScript({ url: aPacket.url, startLine: aPacket.startLine }, true);
|
this._addScript({ url: aPacket.url, startLine: aPacket.startLine }, true);
|
||||||
|
|
||||||
|
// Select the script if it's the preferred one.
|
||||||
|
if (aPacket.url === DebuggerView.Scripts.preferredScriptUrl) {
|
||||||
|
DebuggerView.Scripts.selectScript(aPacket.url);
|
||||||
|
}
|
||||||
|
|
||||||
// If there are any stored breakpoints for this script, display them again,
|
// If there are any stored breakpoints for this script, display them again,
|
||||||
// both in the editor and the pane.
|
// both in the editor and the pane.
|
||||||
for each (let breakpoint in DebuggerController.Breakpoints.store) {
|
for each (let breakpoint in DebuggerController.Breakpoints.store) {
|
||||||
@ -926,6 +931,14 @@ SourceScripts.prototype = {
|
|||||||
}
|
}
|
||||||
DebuggerView.Scripts.commitScripts();
|
DebuggerView.Scripts.commitScripts();
|
||||||
DebuggerController.Breakpoints.updatePaneBreakpoints();
|
DebuggerController.Breakpoints.updatePaneBreakpoints();
|
||||||
|
|
||||||
|
// Select the preferred script if one exists, the first entry otherwise.
|
||||||
|
let preferredScriptUrl = DebuggerView.Scripts.preferredScriptUrl;
|
||||||
|
if (preferredScriptUrl) {
|
||||||
|
DebuggerView.Scripts.selectScript(preferredScriptUrl);
|
||||||
|
} else {
|
||||||
|
DebuggerView.Scripts.selectIndex(0);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -239,6 +239,16 @@ ScriptsView.prototype = {
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the script with the specified index from the list.
|
||||||
|
*
|
||||||
|
* @param number aIndex
|
||||||
|
* The script index.
|
||||||
|
*/
|
||||||
|
selectIndex: function DVS_selectIndex(aIndex) {
|
||||||
|
this._scripts.selectedIndex = aIndex;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects the script with the specified URL from the list.
|
* Selects the script with the specified URL from the list.
|
||||||
*
|
*
|
||||||
@ -277,6 +287,13 @@ ScriptsView.prototype = {
|
|||||||
this._scripts.selectedItem.value : null;
|
this._scripts.selectedItem.value : null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the most recently selected script url.
|
||||||
|
* @return string | null
|
||||||
|
*/
|
||||||
|
get preferredScriptUrl()
|
||||||
|
this._preferredScriptUrl ? this._preferredScriptUrl : null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of labels in the scripts container.
|
* Returns the list of labels in the scripts container.
|
||||||
* @return array
|
* @return array
|
||||||
@ -345,7 +362,7 @@ ScriptsView.prototype = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The script is alphabetically the last one.
|
// The script is alphabetically the last one.
|
||||||
this._createScriptElement(aLabel, aScript, -1, true);
|
this._createScriptElement(aLabel, aScript, -1);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -365,7 +382,7 @@ ScriptsView.prototype = {
|
|||||||
|
|
||||||
for (let i = 0, l = newScripts.length; i < l; i++) {
|
for (let i = 0, l = newScripts.length; i < l; i++) {
|
||||||
let item = newScripts[i];
|
let item = newScripts[i];
|
||||||
this._createScriptElement(item.label, item.script, -1, true);
|
this._createScriptElement(item.label, item.script, -1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -380,12 +397,8 @@ ScriptsView.prototype = {
|
|||||||
* @param number aIndex
|
* @param number aIndex
|
||||||
* The index where to insert to new script in the container.
|
* The index where to insert to new script in the container.
|
||||||
* Pass -1 to append the script at the end.
|
* Pass -1 to append the script at the end.
|
||||||
* @param boolean aSelectIfEmptyFlag
|
|
||||||
* True to set the newly created script as the currently selected item
|
|
||||||
* if there are no other existing scripts in the container.
|
|
||||||
*/
|
*/
|
||||||
_createScriptElement: function DVS__createScriptElement(
|
_createScriptElement: function DVS__createScriptElement(aLabel, aScript, aIndex)
|
||||||
aLabel, aScript, aIndex, aSelectIfEmptyFlag)
|
|
||||||
{
|
{
|
||||||
// Make sure we don't duplicate anything.
|
// Make sure we don't duplicate anything.
|
||||||
if (aLabel == "null" || this.containsLabel(aLabel) || this.contains(aScript.url)) {
|
if (aLabel == "null" || this.containsLabel(aLabel) || this.contains(aScript.url)) {
|
||||||
@ -398,10 +411,6 @@ ScriptsView.prototype = {
|
|||||||
|
|
||||||
scriptItem.setAttribute("tooltiptext", aScript.url);
|
scriptItem.setAttribute("tooltiptext", aScript.url);
|
||||||
scriptItem.setUserData("sourceScript", aScript, null);
|
scriptItem.setUserData("sourceScript", aScript, null);
|
||||||
|
|
||||||
if (this._scripts.itemCount == 1 && aSelectIfEmptyFlag) {
|
|
||||||
this._scripts.selectedItem = scriptItem;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -437,6 +446,7 @@ ScriptsView.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._preferredScript = selectedItem;
|
this._preferredScript = selectedItem;
|
||||||
|
this._preferredScriptUrl = selectedItem.value;
|
||||||
this._scripts.setAttribute("tooltiptext", selectedItem.value);
|
this._scripts.setAttribute("tooltiptext", selectedItem.value);
|
||||||
DebuggerController.SourceScripts.showScript(selectedItem.getUserData("sourceScript"));
|
DebuggerController.SourceScripts.showScript(selectedItem.getUserData("sourceScript"));
|
||||||
},
|
},
|
||||||
|
@ -35,6 +35,7 @@ MOCHITEST_BROWSER_TESTS = \
|
|||||||
browser_dbg_propertyview-09.js \
|
browser_dbg_propertyview-09.js \
|
||||||
browser_dbg_propertyview-10.js \
|
browser_dbg_propertyview-10.js \
|
||||||
browser_dbg_propertyview-edit.js \
|
browser_dbg_propertyview-edit.js \
|
||||||
|
browser_dbg_reload-same-script.js \
|
||||||
browser_dbg_panesize.js \
|
browser_dbg_panesize.js \
|
||||||
browser_dbg_panesize-inner.js \
|
browser_dbg_panesize-inner.js \
|
||||||
browser_dbg_stack-01.js \
|
browser_dbg_stack-01.js \
|
||||||
|
126
browser/devtools/debugger/test/browser_dbg_reload-same-script.js
Normal file
126
browser/devtools/debugger/test/browser_dbg_reload-same-script.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests if the same script is shown after a page is reloaded.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TAB_URL = EXAMPLE_URL + "browser_dbg_script-switching.html";
|
||||||
|
|
||||||
|
let gPane = null;
|
||||||
|
let gTab = null;
|
||||||
|
let gDebuggee = null;
|
||||||
|
let gDebugger = null;
|
||||||
|
let gView = null;
|
||||||
|
|
||||||
|
function test()
|
||||||
|
{
|
||||||
|
let step = 0;
|
||||||
|
let scriptShown = false;
|
||||||
|
let scriptShownUrl = null;
|
||||||
|
let resumed = false;
|
||||||
|
let testStarted = false;
|
||||||
|
|
||||||
|
debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) {
|
||||||
|
gTab = aTab;
|
||||||
|
gDebuggee = aDebuggee;
|
||||||
|
gPane = aPane;
|
||||||
|
gDebugger = gPane.contentWindow;
|
||||||
|
gView = gDebugger.DebuggerView;
|
||||||
|
resumed = true;
|
||||||
|
|
||||||
|
executeSoon(startTest);
|
||||||
|
});
|
||||||
|
|
||||||
|
function onScriptShown(aEvent)
|
||||||
|
{
|
||||||
|
scriptShown = aEvent.detail.url.indexOf("-01.js") != -1;
|
||||||
|
scriptShownUrl = aEvent.detail.url;
|
||||||
|
executeSoon(startTest);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUlteriorScriptShown(aEvent)
|
||||||
|
{
|
||||||
|
scriptShownUrl = aEvent.detail.url;
|
||||||
|
executeSoon(testScriptShown);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("Debugger:ScriptShown", onScriptShown);
|
||||||
|
|
||||||
|
function startTest()
|
||||||
|
{
|
||||||
|
if (scriptShown && resumed && !testStarted) {
|
||||||
|
window.removeEventListener("Debugger:ScriptShown", onScriptShown);
|
||||||
|
window.addEventListener("Debugger:ScriptShown", onUlteriorScriptShown);
|
||||||
|
testStarted = true;
|
||||||
|
Services.tm.currentThread.dispatch({ run: performTest }, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishTest()
|
||||||
|
{
|
||||||
|
if (scriptShown && resumed && testStarted) {
|
||||||
|
window.removeEventListener("Debugger:ScriptShown", onUlteriorScriptShown);
|
||||||
|
closeDebuggerAndFinish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function performTest()
|
||||||
|
{
|
||||||
|
testCurrentScript("-01.js", step);
|
||||||
|
step = 1;
|
||||||
|
reloadPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testScriptShown()
|
||||||
|
{
|
||||||
|
if (step === 1) {
|
||||||
|
testCurrentScript("-01.js", step);
|
||||||
|
step = 2;
|
||||||
|
reloadPage();
|
||||||
|
}
|
||||||
|
else if (step === 2) {
|
||||||
|
testCurrentScript("-01.js", step);
|
||||||
|
step = 3;
|
||||||
|
gView.Scripts.selectScript(gView.Scripts.scriptLocations[1]);
|
||||||
|
}
|
||||||
|
else if (step === 3) {
|
||||||
|
testCurrentScript("-02.js", step);
|
||||||
|
step = 4;
|
||||||
|
reloadPage();
|
||||||
|
}
|
||||||
|
else if (step === 4) {
|
||||||
|
testCurrentScript("-02.js", step);
|
||||||
|
finishTest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCurrentScript(part, step)
|
||||||
|
{
|
||||||
|
info("Currently preferred script: " + gView.Scripts.preferredScriptUrl);
|
||||||
|
info("Currently selected script: " + gView.Scripts.selected);
|
||||||
|
|
||||||
|
isnot(gView.Scripts.preferredScriptUrl.indexOf(part), -1,
|
||||||
|
"The preferred script url wasn't set correctly. (" + step + ")");
|
||||||
|
isnot(gView.Scripts.selected.indexOf(part), -1,
|
||||||
|
"The selected script isn't the correct one. (" + step + ")");
|
||||||
|
is(gView.Scripts.selected, scriptShownUrl,
|
||||||
|
"The shown script is not the the correct one. (" + step + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadPage()
|
||||||
|
{
|
||||||
|
executeSoon(function() {
|
||||||
|
gDebuggee.location.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCleanupFunction(function() {
|
||||||
|
removeTab(gTab);
|
||||||
|
gPane = null;
|
||||||
|
gTab = null;
|
||||||
|
gDebuggee = null;
|
||||||
|
gDebugger = null;
|
||||||
|
gView = null;
|
||||||
|
});
|
||||||
|
}
|
@ -519,7 +519,11 @@ TreePanel.prototype = {
|
|||||||
*/
|
*/
|
||||||
closeEditor: function TP_closeEditor()
|
closeEditor: function TP_closeEditor()
|
||||||
{
|
{
|
||||||
|
if (!this.treeBrowserDocument) // already closed, bug 706092
|
||||||
|
return;
|
||||||
|
|
||||||
let editor = this.treeBrowserDocument.getElementById("attribute-editor");
|
let editor = this.treeBrowserDocument.getElementById("attribute-editor");
|
||||||
|
|
||||||
let editorInput =
|
let editorInput =
|
||||||
this.treeBrowserDocument.getElementById("attribute-editor-input");
|
this.treeBrowserDocument.getElementById("attribute-editor-input");
|
||||||
|
|
||||||
|
@ -11,7 +11,11 @@ let div;
|
|||||||
let editorTestSteps;
|
let editorTestSteps;
|
||||||
|
|
||||||
function doNextStep() {
|
function doNextStep() {
|
||||||
editorTestSteps.next();
|
try {
|
||||||
|
editorTestSteps.next();
|
||||||
|
} catch(exception) {
|
||||||
|
info("caught:", exception);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupEditorTests()
|
function setupEditorTests()
|
||||||
@ -214,16 +218,33 @@ function doEditorTestSteps()
|
|||||||
|
|
||||||
yield; // End of Step 7
|
yield; // End of Step 7
|
||||||
|
|
||||||
|
|
||||||
// Step 8: validate that the editor was closed and that the editing was not saved
|
// Step 8: validate that the editor was closed and that the editing was not saved
|
||||||
ok(!treePanel.editingContext, "Step 8: editor session ended");
|
ok(!treePanel.editingContext, "Step 8: editor session ended");
|
||||||
editorVisible = editor.classList.contains("editing");
|
editorVisible = editor.classList.contains("editing");
|
||||||
ok(!editorVisible, "editor popup hidden");
|
ok(!editorVisible, "editor popup hidden");
|
||||||
is(div.getAttribute("id"), "Hello World", "`id` attribute-value *not* updated");
|
is(div.getAttribute("id"), "Hello World", "`id` attribute-value *not* updated");
|
||||||
is(attrValNode_id.innerHTML, "Hello World", "attribute-value node in HTML panel *not* updated");
|
is(attrValNode_id.innerHTML, "Hello World", "attribute-value node in HTML panel *not* updated");
|
||||||
|
executeSoon(doNextStep);
|
||||||
|
|
||||||
// End of Step 8
|
yield; // End of Step 8
|
||||||
executeSoon(finishUp);
|
|
||||||
|
// Step 9: Open the Editor and verify that closing the tree panel does not make the
|
||||||
|
// Inspector go cray-cray.
|
||||||
|
executeSoon(function() {
|
||||||
|
// firing 2 clicks right in a row to simulate a double-click
|
||||||
|
EventUtils.synthesizeMouse(attrValNode_id, 2, 2, {clickCount: 2}, attrValNode_id.ownerDocument.defaultView);
|
||||||
|
doNextStep();
|
||||||
|
});
|
||||||
|
|
||||||
|
yield; // End of Step 9
|
||||||
|
|
||||||
|
ok(treePanel.editingContext, "Step 9: editor session started");
|
||||||
|
editorVisible = editor.classList.contains("editing");
|
||||||
|
ok(editorVisible, "editor popup is visible");
|
||||||
|
executeSoon(function() {
|
||||||
|
InspectorUI.toggleHTMLPanel();
|
||||||
|
finishUp();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishUp() {
|
function finishUp() {
|
||||||
|
@ -491,9 +491,24 @@ var Scratchpad = {
|
|||||||
*/
|
*/
|
||||||
writeAsErrorComment: function SP_writeAsErrorComment(aError)
|
writeAsErrorComment: function SP_writeAsErrorComment(aError)
|
||||||
{
|
{
|
||||||
let stack = aError.stack || aError.fileName + ":" + aError.lineNumber;
|
let stack = "";
|
||||||
let newComment = "Exception: " + aError.message + "\n" + stack.replace(/\n$/, "");
|
if (aError.stack) {
|
||||||
|
stack = aError.stack;
|
||||||
|
}
|
||||||
|
else if (aError.fileName) {
|
||||||
|
if (aError.lineNumber) {
|
||||||
|
stack = "@" + aError.fileName + ":" + aError.lineNumber;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stack = "@" + aError.fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (aError.lineNumber) {
|
||||||
|
stack = "@" + aError.lineNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newComment = "Exception: " + ( aError.message || aError) + ( stack == "" ? stack : "\n" + stack.replace(/\n$/, "") );
|
||||||
|
|
||||||
this.writeAsComment(newComment);
|
this.writeAsComment(newComment);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ MOCHITEST_BROWSER_FILES = \
|
|||||||
browser_scratchpad_bug714942_goto_line_ui.js \
|
browser_scratchpad_bug714942_goto_line_ui.js \
|
||||||
browser_scratchpad_bug_650760_help_key.js \
|
browser_scratchpad_bug_650760_help_key.js \
|
||||||
browser_scratchpad_bug_651942_recent_files.js \
|
browser_scratchpad_bug_651942_recent_files.js \
|
||||||
|
browser_scratchpad_bug756681_display_non_error_exceptions.js \
|
||||||
head.js \
|
head.js \
|
||||||
|
|
||||||
include $(topsrcdir)/config/rules.mk
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
@ -0,0 +1,104 @@
|
|||||||
|
/* vim: set ts=2 et sw=2 tw=80: */
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
function test()
|
||||||
|
{
|
||||||
|
waitForExplicitFinish();
|
||||||
|
|
||||||
|
gBrowser.selectedTab = gBrowser.addTab();
|
||||||
|
gBrowser.selectedBrowser.addEventListener("load", function browserLoad() {
|
||||||
|
gBrowser.selectedBrowser.removeEventListener("load", browserLoad, true);
|
||||||
|
openScratchpad(runTests, {"state":{"text":""}});
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
content.location = "data:text/html, test that exceptions are output as " +
|
||||||
|
"comments correctly in Scratchpad";
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTests()
|
||||||
|
{
|
||||||
|
var scratchpad = gScratchpadWindow.Scratchpad;
|
||||||
|
|
||||||
|
var message = "\"Hello World!\""
|
||||||
|
var openComment = "\n/*\n";
|
||||||
|
var closeComment = "\n*/";
|
||||||
|
var error1 = "throw new Error(\"Ouch!\")";
|
||||||
|
var error2 = "throw \"A thrown string\"";
|
||||||
|
var error3 = "throw {}";
|
||||||
|
var error4 = "document.body.appendChild(document.body)";
|
||||||
|
let messageArray = {};
|
||||||
|
let count = {};
|
||||||
|
|
||||||
|
// Display message
|
||||||
|
scratchpad.setText(message);
|
||||||
|
scratchpad.display();
|
||||||
|
is(scratchpad.getText(),
|
||||||
|
message + openComment + "Hello World!" + closeComment,
|
||||||
|
"message display output");
|
||||||
|
|
||||||
|
// Display error1, throw new Error("Ouch")
|
||||||
|
scratchpad.setText(error1);
|
||||||
|
scratchpad.display();
|
||||||
|
is(scratchpad.getText(),
|
||||||
|
error1 + openComment + "Exception: Ouch!\n@Scratchpad:1" + closeComment,
|
||||||
|
"error display output");
|
||||||
|
|
||||||
|
// Display error2, throw "A thrown string"
|
||||||
|
scratchpad.setText(error2);
|
||||||
|
scratchpad.display();
|
||||||
|
is(scratchpad.getText(),
|
||||||
|
error2 + openComment + "Exception: A thrown string" + closeComment,
|
||||||
|
"thrown string display output");
|
||||||
|
|
||||||
|
// Display error3, throw {}
|
||||||
|
scratchpad.setText(error3);
|
||||||
|
scratchpad.display();
|
||||||
|
is(scratchpad.getText(),
|
||||||
|
error3 + openComment + "Exception: [object Object]" + closeComment,
|
||||||
|
"thrown object display output");
|
||||||
|
|
||||||
|
// Display error4, document.body.appendChild(document.body)
|
||||||
|
scratchpad.setText(error4);
|
||||||
|
scratchpad.display();
|
||||||
|
is(scratchpad.getText(),
|
||||||
|
error4 + openComment + "Exception: Node cannot be inserted " +
|
||||||
|
"at the specified point in the hierarchy\n@1" + closeComment,
|
||||||
|
"Alternative format error display output");
|
||||||
|
|
||||||
|
// Run message
|
||||||
|
scratchpad.setText(message);
|
||||||
|
scratchpad.run();
|
||||||
|
is(scratchpad.getText(), message, "message run output");
|
||||||
|
|
||||||
|
// Run error1, throw new Error("Ouch")
|
||||||
|
scratchpad.setText(error1);
|
||||||
|
scratchpad.run();
|
||||||
|
is(scratchpad.getText(),
|
||||||
|
error1 + openComment + "Exception: Ouch!\n@Scratchpad:1" + closeComment,
|
||||||
|
"error run output");
|
||||||
|
|
||||||
|
// Run error2, throw "A thrown string"
|
||||||
|
scratchpad.setText(error2);
|
||||||
|
scratchpad.run();
|
||||||
|
is(scratchpad.getText(),
|
||||||
|
error2 + openComment + "Exception: A thrown string" + closeComment,
|
||||||
|
"thrown string run output");
|
||||||
|
|
||||||
|
// Run error3, throw {}
|
||||||
|
scratchpad.setText(error3);
|
||||||
|
scratchpad.run();
|
||||||
|
is(scratchpad.getText(),
|
||||||
|
error3 + openComment + "Exception: [object Object]" + closeComment,
|
||||||
|
"thrown object run output");
|
||||||
|
|
||||||
|
// Run error4, document.body.appendChild(document.body)
|
||||||
|
scratchpad.setText(error4);
|
||||||
|
scratchpad.run();
|
||||||
|
is(scratchpad.getText(),
|
||||||
|
error4 + openComment + "Exception: Node cannot be inserted " +
|
||||||
|
"at the specified point in the hierarchy\n@1" + closeComment,
|
||||||
|
"Alternative format error run output");
|
||||||
|
|
||||||
|
finish();
|
||||||
|
}
|
@ -523,11 +523,11 @@ function JSTermHelper(aJSTerm)
|
|||||||
* @param string aId
|
* @param string aId
|
||||||
* The ID of the element you want.
|
* The ID of the element you want.
|
||||||
* @return nsIDOMNode or null
|
* @return nsIDOMNode or null
|
||||||
* The result of calling document.getElementById(aId).
|
* The result of calling document.querySelector(aSelector).
|
||||||
*/
|
*/
|
||||||
aJSTerm.sandbox.$ = function JSTH_$(aId)
|
aJSTerm.sandbox.$ = function JSTH_$(aSelector)
|
||||||
{
|
{
|
||||||
return aJSTerm.window.document.getElementById(aId);
|
return aJSTerm.window.document.querySelector(aSelector);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -611,7 +611,6 @@ WebConsole.prototype = {
|
|||||||
|
|
||||||
let position = Services.prefs.getCharPref("devtools.webconsole.position");
|
let position = Services.prefs.getCharPref("devtools.webconsole.position");
|
||||||
this.positionConsole(position);
|
this.positionConsole(position);
|
||||||
this._currentUIPosition = position;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -622,8 +621,10 @@ WebConsole.prototype = {
|
|||||||
{
|
{
|
||||||
this.iframe.removeEventListener("load", this._onIframeLoad, true);
|
this.iframe.removeEventListener("load", this._onIframeLoad, true);
|
||||||
|
|
||||||
|
let position = Services.prefs.getCharPref("devtools.webconsole.position");
|
||||||
|
|
||||||
this.iframeWindow = this.iframe.contentWindow.wrappedJSObject;
|
this.iframeWindow = this.iframe.contentWindow.wrappedJSObject;
|
||||||
this.ui = new this.iframeWindow.WebConsoleFrame(this, this._currentUIPosition);
|
this.ui = new this.iframeWindow.WebConsoleFrame(this, position);
|
||||||
this._setupMessageManager();
|
this._setupMessageManager();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -695,8 +696,6 @@ WebConsole.prototype = {
|
|||||||
this.iframe.flex = 1;
|
this.iframe.flex = 1;
|
||||||
|
|
||||||
panel.setAttribute("height", height);
|
panel.setAttribute("height", height);
|
||||||
|
|
||||||
this._afterPositionConsole("window", lastIndex);
|
|
||||||
}).bind(this);
|
}).bind(this);
|
||||||
|
|
||||||
panel.addEventListener("popupshown", onPopupShown,false);
|
panel.addEventListener("popupshown", onPopupShown,false);
|
||||||
@ -736,6 +735,9 @@ WebConsole.prototype = {
|
|||||||
if (this.splitter.parentNode) {
|
if (this.splitter.parentNode) {
|
||||||
this.splitter.parentNode.removeChild(this.splitter);
|
this.splitter.parentNode.removeChild(this.splitter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._beforePositionConsole("window", lastIndex);
|
||||||
|
|
||||||
panel.appendChild(this.iframe);
|
panel.appendChild(this.iframe);
|
||||||
|
|
||||||
let space = this.chromeDocument.createElement("spacer");
|
let space = this.chromeDocument.createElement("spacer");
|
||||||
@ -822,6 +824,8 @@ WebConsole.prototype = {
|
|||||||
this.splitter.parentNode.removeChild(this.splitter);
|
this.splitter.parentNode.removeChild(this.splitter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._beforePositionConsole(aPosition, lastIndex);
|
||||||
|
|
||||||
if (aPosition == "below") {
|
if (aPosition == "below") {
|
||||||
nBox.appendChild(this.splitter);
|
nBox.appendChild(this.splitter);
|
||||||
nBox.appendChild(this.iframe);
|
nBox.appendChild(this.iframe);
|
||||||
@ -841,12 +845,10 @@ WebConsole.prototype = {
|
|||||||
this.iframe.removeAttribute("height");
|
this.iframe.removeAttribute("height");
|
||||||
this.iframe.style.height = height + "px";
|
this.iframe.style.height = height + "px";
|
||||||
}
|
}
|
||||||
|
|
||||||
this._afterPositionConsole(aPosition, lastIndex);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common code that needs to execute after the Web Console is repositioned.
|
* Common code that needs to execute before the Web Console is repositioned.
|
||||||
* @private
|
* @private
|
||||||
* @param string aPosition
|
* @param string aPosition
|
||||||
* The new position: "above", "below" or "window".
|
* The new position: "above", "below" or "window".
|
||||||
@ -854,8 +856,8 @@ WebConsole.prototype = {
|
|||||||
* The last visible message in the console output before repositioning
|
* The last visible message in the console output before repositioning
|
||||||
* occurred.
|
* occurred.
|
||||||
*/
|
*/
|
||||||
_afterPositionConsole:
|
_beforePositionConsole:
|
||||||
function WC__afterPositionConsole(aPosition, aLastIndex)
|
function WC__beforePositionConsole(aPosition, aLastIndex)
|
||||||
{
|
{
|
||||||
if (!this.ui) {
|
if (!this.ui) {
|
||||||
return;
|
return;
|
||||||
|
@ -52,7 +52,7 @@ function testJSTerm(hud)
|
|||||||
jsterm = hud.jsterm;
|
jsterm = hud.jsterm;
|
||||||
|
|
||||||
jsterm.clearOutput();
|
jsterm.clearOutput();
|
||||||
jsterm.execute("'id=' + $('header').getAttribute('id')");
|
jsterm.execute("'id=' + $('#header').getAttribute('id')");
|
||||||
checkResult('"id=header"', "$() worked", 1);
|
checkResult('"id=header"', "$() worked", 1);
|
||||||
yield;
|
yield;
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ function waitForPosition(aPosition, aCallback) {
|
|||||||
{
|
{
|
||||||
return hudRef._currentUIPosition == aPosition;
|
return hudRef._currentUIPosition == aPosition;
|
||||||
},
|
},
|
||||||
successFn: aCallback,
|
successFn: executeSoon.bind(null, aCallback),
|
||||||
failureFn: finishTest,
|
failureFn: finishTest,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -55,9 +55,10 @@ function consoleOpened(aHudRef) {
|
|||||||
"position menu checkbox is below");
|
"position menu checkbox is below");
|
||||||
is(Services.prefs.getCharPref(POSITION_PREF), "below", "pref is below");
|
is(Services.prefs.getCharPref(POSITION_PREF), "below", "pref is below");
|
||||||
|
|
||||||
hudRef.positionConsole("above");
|
executeSoon(function() {
|
||||||
|
hudRef.positionConsole("above");
|
||||||
waitForPosition("above", onPositionAbove);
|
waitForPosition("above", onPositionAbove);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPositionAbove() {
|
function onPositionAbove() {
|
||||||
@ -81,8 +82,10 @@ function onPositionAbove() {
|
|||||||
Services.prefs.setIntPref(TOP_PREF, 50);
|
Services.prefs.setIntPref(TOP_PREF, 50);
|
||||||
Services.prefs.setIntPref(LEFT_PREF, 51);
|
Services.prefs.setIntPref(LEFT_PREF, 51);
|
||||||
|
|
||||||
hudRef.positionConsole("window");
|
executeSoon(function() {
|
||||||
waitForPosition("window", onPositionWindow);
|
hudRef.positionConsole("window");
|
||||||
|
waitForPosition("window", onPositionWindow);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPositionWindow() {
|
function onPositionWindow() {
|
||||||
|
@ -819,7 +819,7 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t
|
|||||||
self.log.info("Can't trigger Breakpad, just killing process")
|
self.log.info("Can't trigger Breakpad, just killing process")
|
||||||
proc.kill()
|
proc.kill()
|
||||||
|
|
||||||
def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsPath, logger):
|
def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsPath):
|
||||||
""" Look for timeout or crashes and return the status after the process terminates """
|
""" Look for timeout or crashes and return the status after the process terminates """
|
||||||
stackFixerProcess = None
|
stackFixerProcess = None
|
||||||
stackFixerFunction = None
|
stackFixerFunction = None
|
||||||
@ -856,8 +856,6 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t
|
|||||||
if stackFixerFunction:
|
if stackFixerFunction:
|
||||||
line = stackFixerFunction(line)
|
line = stackFixerFunction(line)
|
||||||
self.log.info(line.rstrip().decode("UTF-8", "ignore"))
|
self.log.info(line.rstrip().decode("UTF-8", "ignore"))
|
||||||
if logger:
|
|
||||||
logger.log(line)
|
|
||||||
if "TEST-START" in line and "|" in line:
|
if "TEST-START" in line and "|" in line:
|
||||||
self.lastTestSeen = line.split("|")[1].strip()
|
self.lastTestSeen = line.split("|")[1].strip()
|
||||||
if not debuggerInfo and "TEST-UNEXPECTED-FAIL" in line and "Test timed out" in line:
|
if not debuggerInfo and "TEST-UNEXPECTED-FAIL" in line and "Test timed out" in line:
|
||||||
@ -947,7 +945,7 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t
|
|||||||
|
|
||||||
def runApp(self, testURL, env, app, profileDir, extraArgs,
|
def runApp(self, testURL, env, app, profileDir, extraArgs,
|
||||||
runSSLTunnel = False, utilityPath = None,
|
runSSLTunnel = False, utilityPath = None,
|
||||||
xrePath = None, certPath = None, logger = None,
|
xrePath = None, certPath = None,
|
||||||
debuggerInfo = None, symbolsPath = None,
|
debuggerInfo = None, symbolsPath = None,
|
||||||
timeout = -1, maxTime = None):
|
timeout = -1, maxTime = None):
|
||||||
"""
|
"""
|
||||||
@ -1006,7 +1004,7 @@ user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless t
|
|||||||
stderr = subprocess.STDOUT)
|
stderr = subprocess.STDOUT)
|
||||||
self.log.info("INFO | automation.py | Application pid: %d", proc.pid)
|
self.log.info("INFO | automation.py | Application pid: %d", proc.pid)
|
||||||
|
|
||||||
status = self.waitForFinish(proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsPath, logger)
|
status = self.waitForFinish(proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsPath)
|
||||||
self.log.info("INFO | automation.py | Application ran for: %s", str(datetime.now() - startTime))
|
self.log.info("INFO | automation.py | Application ran for: %s", str(datetime.now() - startTime))
|
||||||
|
|
||||||
# Do a final check for zombie child processes.
|
# Do a final check for zombie child processes.
|
||||||
|
@ -7,7 +7,6 @@ from __future__ import with_statement
|
|||||||
import glob, logging, os, platform, shutil, subprocess, sys, tempfile, urllib2, zipfile
|
import glob, logging, os, platform, shutil, subprocess, sys, tempfile, urllib2, zipfile
|
||||||
import re
|
import re
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
from operator import itemgetter
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"ZipFileReader",
|
"ZipFileReader",
|
||||||
@ -20,7 +19,6 @@ __all__ = [
|
|||||||
"DEBUGGER_INFO",
|
"DEBUGGER_INFO",
|
||||||
"replaceBackSlashes",
|
"replaceBackSlashes",
|
||||||
"wrapCommand",
|
"wrapCommand",
|
||||||
"ShutdownLeakLogger"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Map of debugging programs to information about them, like default arguments
|
# Map of debugging programs to information about them, like default arguments
|
||||||
@ -419,115 +417,3 @@ def wrapCommand(cmd):
|
|||||||
return ["arch", "-arch", "i386"] + cmd
|
return ["arch", "-arch", "i386"] + cmd
|
||||||
# otherwise just execute the command normally
|
# otherwise just execute the command normally
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
class ShutdownLeakLogger(object):
|
|
||||||
"""
|
|
||||||
Parses the mochitest run log when running a debug build, assigns all leaked
|
|
||||||
DOM windows (that are still around after test suite shutdown, despite running
|
|
||||||
the GC) to the tests that created them and prints leak statistics.
|
|
||||||
"""
|
|
||||||
MAX_LEAK_COUNT = 5
|
|
||||||
|
|
||||||
def __init__(self, logger):
|
|
||||||
self.logger = logger
|
|
||||||
self.tests = []
|
|
||||||
self.leakedWindows = {}
|
|
||||||
self.leakedDocShells = set()
|
|
||||||
self.currentTest = None
|
|
||||||
self.seenShutdown = False
|
|
||||||
|
|
||||||
def log(self, line):
|
|
||||||
if line[2:11] == "DOMWINDOW":
|
|
||||||
self._logWindow(line)
|
|
||||||
elif line[2:10] == "DOCSHELL":
|
|
||||||
self._logDocShell(line)
|
|
||||||
elif line.startswith("TEST-START"):
|
|
||||||
fileName = line.split(" ")[-1].strip().replace("chrome://mochitests/content/browser/", "")
|
|
||||||
self.currentTest = {"fileName": fileName, "windows": set(), "docShells": set()}
|
|
||||||
elif line.startswith("INFO TEST-END"):
|
|
||||||
# don't track a test if no windows or docShells leaked
|
|
||||||
if self.currentTest and (self.currentTest["windows"] or self.currentTest["docShells"]):
|
|
||||||
self.tests.append(self.currentTest)
|
|
||||||
self.currentTest = None
|
|
||||||
elif line.startswith("INFO TEST-START | Shutdown"):
|
|
||||||
self.seenShutdown = True
|
|
||||||
|
|
||||||
def parse(self):
|
|
||||||
leakingTests = self._parseLeakingTests()
|
|
||||||
|
|
||||||
if leakingTests:
|
|
||||||
totalWindows = sum(len(test["leakedWindows"]) for test in leakingTests)
|
|
||||||
totalDocShells = sum(len(test["leakedDocShells"]) for test in leakingTests)
|
|
||||||
msgType = "TEST-INFO" if totalWindows + totalDocShells <= self.MAX_LEAK_COUNT else "TEST-UNEXPECTED-FAIL"
|
|
||||||
self.logger.info("%s | ShutdownLeaks | leaked %d DOMWindow(s) and %d DocShell(s) until shutdown", msgType, totalWindows, totalDocShells)
|
|
||||||
|
|
||||||
for test in leakingTests:
|
|
||||||
for url, count in self._zipLeakedWindows(test["leakedWindows"]):
|
|
||||||
self.logger.info("%s | %s | leaked %d window(s) until shutdown [url = %s]", msgType, test["fileName"], count, url)
|
|
||||||
|
|
||||||
if test["leakedDocShells"]:
|
|
||||||
self.logger.info("%s | %s | leaked %d docShell(s) until shutdown", msgType, test["fileName"], len(test["leakedDocShells"]))
|
|
||||||
|
|
||||||
def _logWindow(self, line):
|
|
||||||
created = line[:2] == "++"
|
|
||||||
id = self._parseValue(line, "serial")
|
|
||||||
|
|
||||||
# log line has invalid format
|
|
||||||
if not id:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.currentTest:
|
|
||||||
windows = self.currentTest["windows"]
|
|
||||||
if created:
|
|
||||||
windows.add(id)
|
|
||||||
else:
|
|
||||||
windows.discard(id)
|
|
||||||
elif self.seenShutdown and not created:
|
|
||||||
self.leakedWindows[id] = self._parseValue(line, "url")
|
|
||||||
|
|
||||||
def _logDocShell(self, line):
|
|
||||||
created = line[:2] == "++"
|
|
||||||
id = self._parseValue(line, "id")
|
|
||||||
|
|
||||||
# log line has invalid format
|
|
||||||
if not id:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.currentTest:
|
|
||||||
docShells = self.currentTest["docShells"]
|
|
||||||
if created:
|
|
||||||
docShells.add(id)
|
|
||||||
else:
|
|
||||||
docShells.discard(id)
|
|
||||||
elif self.seenShutdown and not created:
|
|
||||||
self.leakedDocShells.add(id)
|
|
||||||
|
|
||||||
def _parseValue(self, line, name):
|
|
||||||
match = re.search("\[%s = (.+?)\]" % name, line)
|
|
||||||
if match:
|
|
||||||
return match.group(1)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _parseLeakingTests(self):
|
|
||||||
leakingTests = []
|
|
||||||
|
|
||||||
for test in self.tests:
|
|
||||||
test["leakedWindows"] = [self.leakedWindows[id] for id in test["windows"] if id in self.leakedWindows]
|
|
||||||
test["leakedDocShells"] = [id for id in test["docShells"] if id in self.leakedDocShells]
|
|
||||||
test["leakCount"] = len(test["leakedWindows"]) + len(test["leakedDocShells"])
|
|
||||||
|
|
||||||
if test["leakCount"]:
|
|
||||||
leakingTests.append(test)
|
|
||||||
|
|
||||||
return sorted(leakingTests, key=itemgetter("leakCount"), reverse=True)
|
|
||||||
|
|
||||||
def _zipLeakedWindows(self, leakedWindows):
|
|
||||||
counts = []
|
|
||||||
counted = set()
|
|
||||||
|
|
||||||
for url in leakedWindows:
|
|
||||||
if not url in counted:
|
|
||||||
counts.append((url, leakedWindows.count(url)))
|
|
||||||
counted.add(url)
|
|
||||||
|
|
||||||
return sorted(counts, key=itemgetter(1), reverse=True)
|
|
||||||
|
@ -62,7 +62,7 @@ class RemoteAutomation(Automation):
|
|||||||
|
|
||||||
return env
|
return env
|
||||||
|
|
||||||
def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsDir, logger):
|
def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime, debuggerInfo, symbolsDir):
|
||||||
# maxTime is used to override the default timeout, we should honor that
|
# maxTime is used to override the default timeout, we should honor that
|
||||||
status = proc.wait(timeout = maxTime)
|
status = proc.wait(timeout = maxTime)
|
||||||
|
|
||||||
|
@ -1246,6 +1246,12 @@ NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGlobalWindow)
|
|||||||
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
|
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
|
||||||
|
|
||||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsGlobalWindow)
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsGlobalWindow)
|
||||||
|
if (NS_UNLIKELY(cb.WantDebugInfo())) {
|
||||||
|
char name[512];
|
||||||
|
PR_snprintf(name, sizeof(name), "nsGlobalWindow #%ld", tmp->mWindowID);
|
||||||
|
cb.DescribeRefCountedNode(tmp->mRefCnt.get(), sizeof(nsGlobalWindow), name);
|
||||||
|
}
|
||||||
|
|
||||||
if (!cb.WantAllTraces() && tmp->IsBlackForCC()) {
|
if (!cb.WantAllTraces() && tmp->IsBlackForCC()) {
|
||||||
return NS_SUCCESS_INTERRUPTED_TRAVERSE;
|
return NS_SUCCESS_INTERRUPTED_TRAVERSE;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* function in the loaded actors in order to initialize properly.
|
* function in the loaded actors in order to initialize properly.
|
||||||
*/
|
*/
|
||||||
function createRootActor(aConnection) {
|
function createRootActor(aConnection) {
|
||||||
return new FennecRootActor(aConnection);
|
return new DeviceRootActor(aConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,18 +26,18 @@ function createRootActor(aConnection) {
|
|||||||
* @param aConnection DebuggerServerConnection
|
* @param aConnection DebuggerServerConnection
|
||||||
* The conection to the client.
|
* The conection to the client.
|
||||||
*/
|
*/
|
||||||
function FennecRootActor(aConnection) {
|
function DeviceRootActor(aConnection) {
|
||||||
BrowserRootActor.call(this, aConnection);
|
BrowserRootActor.call(this, aConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
FennecRootActor.prototype = new BrowserRootActor();
|
DeviceRootActor.prototype = new BrowserRootActor();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the listTabs request. Builds a list of actors
|
* Handles the listTabs request. Builds a list of actors
|
||||||
* for the tabs running in the process. The actors will survive
|
* for the tabs running in the process. The actors will survive
|
||||||
* until at least the next listTabs request.
|
* until at least the next listTabs request.
|
||||||
*/
|
*/
|
||||||
FennecRootActor.prototype.onListTabs = function FRA_onListTabs() {
|
DeviceRootActor.prototype.onListTabs = function DRA_onListTabs() {
|
||||||
// Get actors for all the currently-running tabs (reusing
|
// Get actors for all the currently-running tabs (reusing
|
||||||
// existing actors where applicable), and store them in
|
// existing actors where applicable), and store them in
|
||||||
// an ActorPool.
|
// an ActorPool.
|
||||||
@ -92,7 +92,7 @@ FennecRootActor.prototype.onListTabs = function FRA_onListTabs() {
|
|||||||
/**
|
/**
|
||||||
* Return the tab container for the specified window.
|
* Return the tab container for the specified window.
|
||||||
*/
|
*/
|
||||||
FennecRootActor.prototype.getTabContainer = function FRA_getTabContainer(aWindow) {
|
DeviceRootActor.prototype.getTabContainer = function DRA_getTabContainer(aWindow) {
|
||||||
return aWindow.document.getElementById("browsers");
|
return aWindow.document.getElementById("browsers");
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -100,12 +100,12 @@ FennecRootActor.prototype.getTabContainer = function FRA_getTabContainer(aWindow
|
|||||||
* When a tab is closed, exit its tab actor. The actor
|
* When a tab is closed, exit its tab actor. The actor
|
||||||
* will be dropped at the next listTabs request.
|
* will be dropped at the next listTabs request.
|
||||||
*/
|
*/
|
||||||
FennecRootActor.prototype.onTabClosed = function FRA_onTabClosed(aEvent) {
|
DeviceRootActor.prototype.onTabClosed = function DRA_onTabClosed(aEvent) {
|
||||||
this.exitTabActor(aEvent.target.browser);
|
this.exitTabActor(aEvent.target.browser);
|
||||||
};
|
};
|
||||||
|
|
||||||
// nsIWindowMediatorListener
|
// nsIWindowMediatorListener
|
||||||
FennecRootActor.prototype.onCloseWindow = function FRA_onCloseWindow(aWindow) {
|
DeviceRootActor.prototype.onCloseWindow = function DRA_onCloseWindow(aWindow) {
|
||||||
if (aWindow.BrowserApp) {
|
if (aWindow.BrowserApp) {
|
||||||
this.unwatchWindow(aWindow);
|
this.unwatchWindow(aWindow);
|
||||||
}
|
}
|
||||||
@ -114,6 +114,6 @@ FennecRootActor.prototype.onCloseWindow = function FRA_onCloseWindow(aWindow) {
|
|||||||
/**
|
/**
|
||||||
* The request types this actor can handle.
|
* The request types this actor can handle.
|
||||||
*/
|
*/
|
||||||
FennecRootActor.prototype.requestTypes = {
|
DeviceRootActor.prototype.requestTypes = {
|
||||||
"listTabs": FennecRootActor.prototype.onListTabs
|
"listTabs": DeviceRootActor.prototype.onListTabs
|
||||||
};
|
};
|
||||||
|
@ -32,7 +32,14 @@ libs::
|
|||||||
|
|
||||||
TEST_DIRS += tests
|
TEST_DIRS += tests
|
||||||
|
|
||||||
TESTING_JS_MODULES := aitcserver.js storageserver.js
|
testing_modules := \
|
||||||
|
aitcserver.js \
|
||||||
|
storageserver.js \
|
||||||
|
utils.js \
|
||||||
|
$(NULL)
|
||||||
|
|
||||||
|
TESTING_JS_MODULES := $(foreach file,$(testing_modules),modules-testing/$(file))
|
||||||
|
|
||||||
TESTING_JS_MODULE_DIR := services-common
|
TESTING_JS_MODULE_DIR := services-common
|
||||||
|
|
||||||
# What follows is a helper to launch a standalone storage server instance.
|
# What follows is a helper to launch a standalone storage server instance.
|
||||||
|
42
services/common/modules-testing/utils.js
Normal file
42
services/common/modules-testing/utils.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/* 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 EXPORTED_SYMBOLS = [
|
||||||
|
"TestingUtils",
|
||||||
|
];
|
||||||
|
|
||||||
|
let TestingUtils = {
|
||||||
|
/**
|
||||||
|
* Perform a deep copy of an Array or Object.
|
||||||
|
*/
|
||||||
|
deepCopy: function deepCopy(thing, noSort) {
|
||||||
|
if (typeof(thing) != "object" || thing == null) {
|
||||||
|
return thing;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(thing)) {
|
||||||
|
let ret = [];
|
||||||
|
for (let element of thing) {
|
||||||
|
ret.push(this.deepCopy(element, noSort));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret = {};
|
||||||
|
let props = [p for (p in thing)];
|
||||||
|
|
||||||
|
if (!noSort) {
|
||||||
|
props = props.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let prop of props) {
|
||||||
|
ret[prop] = this.deepCopy(thing[prop], noSort);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
};
|
@ -1,8 +1,11 @@
|
|||||||
Cu.import("resource://services-sync/util.js");
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
Cu.import("resource://testing-common/services-common/utils.js");
|
||||||
|
|
||||||
function run_test() {
|
function run_test() {
|
||||||
let thing = {o: {foo: "foo", bar: ["bar"]}, a: ["foo", {bar: "bar"}]};
|
let thing = {o: {foo: "foo", bar: ["bar"]}, a: ["foo", {bar: "bar"}]};
|
||||||
let ret = deepCopy(thing);
|
let ret = TestingUtils.deepCopy(thing);
|
||||||
do_check_neq(ret, thing)
|
do_check_neq(ret, thing)
|
||||||
do_check_neq(ret.o, thing.o);
|
do_check_neq(ret.o, thing.o);
|
||||||
do_check_neq(ret.o.bar, thing.o.bar);
|
do_check_neq(ret.o.bar, thing.o.bar);
|
@ -6,6 +6,7 @@ tail =
|
|||||||
[test_load_modules.js]
|
[test_load_modules.js]
|
||||||
|
|
||||||
[test_utils_atob.js]
|
[test_utils_atob.js]
|
||||||
|
[test_utils_deepCopy.js]
|
||||||
[test_utils_encodeBase32.js]
|
[test_utils_encodeBase32.js]
|
||||||
[test_utils_encodeBase64URL.js]
|
[test_utils_encodeBase64URL.js]
|
||||||
[test_utils_json.js]
|
[test_utils_json.js]
|
||||||
|
474
services/sync/modules/addonutils.js
Normal file
474
services/sync/modules/addonutils.js
Normal file
@ -0,0 +1,474 @@
|
|||||||
|
/* 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 EXPORTED_SYMBOLS = ["AddonUtils"];
|
||||||
|
|
||||||
|
const {interfaces: Ci, utils: Cu} = Components;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
Cu.import("resource://services-common/log4moz.js");
|
||||||
|
Cu.import("resource://services-sync/util.js");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
|
||||||
|
"resource://gre/modules/AddonManager.jsm");
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
|
||||||
|
"resource://gre/modules/AddonRepository.jsm");
|
||||||
|
|
||||||
|
function AddonUtilsInternal() {
|
||||||
|
this._log = Log4Moz.repository.getLogger("Sync.AddonUtils");
|
||||||
|
this._log.Level = Log4Moz.Level[Svc.Prefs.get("log.logger.addonutils")];
|
||||||
|
}
|
||||||
|
AddonUtilsInternal.prototype = {
|
||||||
|
/**
|
||||||
|
* Obtain an AddonInstall object from an AddonSearchResult instance.
|
||||||
|
*
|
||||||
|
* The callback will be invoked with the result of the operation. The
|
||||||
|
* callback receives 2 arguments, error and result. Error will be falsy
|
||||||
|
* on success or some kind of error value otherwise. The result argument
|
||||||
|
* will be an AddonInstall on success or null on failure. It is possible
|
||||||
|
* for the error to be falsy but result to be null. This could happen if
|
||||||
|
* an install was not found.
|
||||||
|
*
|
||||||
|
* @param addon
|
||||||
|
* AddonSearchResult to obtain install from.
|
||||||
|
* @param cb
|
||||||
|
* Function to be called with result of operation.
|
||||||
|
*/
|
||||||
|
getInstallFromSearchResult:
|
||||||
|
function getInstallFromSearchResult(addon, cb, requireSecureURI=true) {
|
||||||
|
|
||||||
|
this._log.debug("Obtaining install for " + addon.id);
|
||||||
|
|
||||||
|
// Verify that the source URI uses TLS. We don't allow installs from
|
||||||
|
// insecure sources for security reasons. The Addon Manager ensures that
|
||||||
|
// cert validation, etc is performed.
|
||||||
|
if (requireSecureURI) {
|
||||||
|
let scheme = addon.sourceURI.scheme;
|
||||||
|
if (scheme != "https") {
|
||||||
|
cb(new Error("Insecure source URI scheme: " + scheme), addon.install);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should theoretically be able to obtain (and use) addon.install if
|
||||||
|
// it is available. However, the addon.sourceURI rewriting won't be
|
||||||
|
// reflected in the AddonInstall, so we can't use it. If we ever get rid
|
||||||
|
// of sourceURI rewriting, we can avoid having to reconstruct the
|
||||||
|
// AddonInstall.
|
||||||
|
AddonManager.getInstallForURL(
|
||||||
|
addon.sourceURI.spec,
|
||||||
|
function handleInstall(install) {
|
||||||
|
cb(null, install);
|
||||||
|
},
|
||||||
|
"application/x-xpinstall",
|
||||||
|
undefined,
|
||||||
|
addon.name,
|
||||||
|
addon.iconURL,
|
||||||
|
addon.version
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs an add-on from an AddonSearchResult instance.
|
||||||
|
*
|
||||||
|
* The options argument defines extra options to control the install.
|
||||||
|
* Recognized keys in this map are:
|
||||||
|
*
|
||||||
|
* syncGUID - Sync GUID to use for the new add-on.
|
||||||
|
* enabled - Boolean indicating whether the add-on should be enabled upon
|
||||||
|
* install.
|
||||||
|
* requireSecureURI - Boolean indicating whether to require a secure
|
||||||
|
* URI to install from. This defaults to true.
|
||||||
|
*
|
||||||
|
* When complete it calls a callback with 2 arguments, error and result.
|
||||||
|
*
|
||||||
|
* If error is falsy, result is an object. If error is truthy, result is
|
||||||
|
* null.
|
||||||
|
*
|
||||||
|
* The result object has the following keys:
|
||||||
|
*
|
||||||
|
* id ID of add-on that was installed.
|
||||||
|
* install AddonInstall that was installed.
|
||||||
|
* addon Addon that was installed.
|
||||||
|
*
|
||||||
|
* @param addon
|
||||||
|
* AddonSearchResult to install add-on from.
|
||||||
|
* @param options
|
||||||
|
* Object with additional metadata describing how to install add-on.
|
||||||
|
* @param cb
|
||||||
|
* Function to be invoked with result of operation.
|
||||||
|
*/
|
||||||
|
installAddonFromSearchResult:
|
||||||
|
function installAddonFromSearchResult(addon, options, cb) {
|
||||||
|
this._log.info("Trying to install add-on from search result: " + addon.id);
|
||||||
|
|
||||||
|
if (options.requireSecureURI === undefined) {
|
||||||
|
options.requireSecureURI = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getInstallFromSearchResult(addon, function onResult(error, install) {
|
||||||
|
if (error) {
|
||||||
|
cb(error, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!install) {
|
||||||
|
cb(new Error("AddonInstall not available: " + addon.id), null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._log.info("Installing " + addon.id);
|
||||||
|
let log = this._log;
|
||||||
|
|
||||||
|
let listener = {
|
||||||
|
onInstallStarted: function onInstallStarted(install) {
|
||||||
|
if (!options) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.syncGUID) {
|
||||||
|
log.info("Setting syncGUID of " + install.name +": " +
|
||||||
|
options.syncGUID);
|
||||||
|
install.addon.syncGUID = options.syncGUID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only need to change userDisabled if it is disabled because
|
||||||
|
// enabled is the default.
|
||||||
|
if ("enabled" in options && !options.enabled) {
|
||||||
|
log.info("Marking add-on as disabled for install: " +
|
||||||
|
install.name);
|
||||||
|
install.addon.userDisabled = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onInstallEnded: function(install, addon) {
|
||||||
|
install.removeListener(listener);
|
||||||
|
|
||||||
|
cb(null, {id: addon.id, install: install, addon: addon});
|
||||||
|
},
|
||||||
|
onInstallFailed: function(install) {
|
||||||
|
install.removeListener(listener);
|
||||||
|
|
||||||
|
cb(new Error("Install failed: " + install.error), null);
|
||||||
|
},
|
||||||
|
onDownloadFailed: function(install) {
|
||||||
|
install.removeListener(listener);
|
||||||
|
|
||||||
|
cb(new Error("Download failed: " + install.error), null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
install.addListener(listener);
|
||||||
|
install.install();
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
this._log.error("Error installing add-on: " + Utils.exceptionstr(ex));
|
||||||
|
cb(ex, null);
|
||||||
|
}
|
||||||
|
}.bind(this), options.requireSecureURI);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uninstalls the Addon instance and invoke a callback when it is done.
|
||||||
|
*
|
||||||
|
* @param addon
|
||||||
|
* Addon instance to uninstall.
|
||||||
|
* @param cb
|
||||||
|
* Function to be invoked when uninstall has finished. It receives a
|
||||||
|
* truthy value signifying error and the add-on which was uninstalled.
|
||||||
|
*/
|
||||||
|
uninstallAddon: function uninstallAddon(addon, cb) {
|
||||||
|
let listener = {
|
||||||
|
onUninstalling: function(uninstalling, needsRestart) {
|
||||||
|
if (addon.id != uninstalling.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We assume restartless add-ons will send the onUninstalled event
|
||||||
|
// soon.
|
||||||
|
if (!needsRestart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-restartless add-ons, we issue the callback on uninstalling
|
||||||
|
// because we will likely never see the uninstalled event.
|
||||||
|
AddonManager.removeAddonListener(listener);
|
||||||
|
cb(null, addon);
|
||||||
|
},
|
||||||
|
onUninstalled: function(uninstalled) {
|
||||||
|
if (addon.id != uninstalled.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddonManager.removeAddonListener(listener);
|
||||||
|
cb(null, addon);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AddonManager.addAddonListener(listener);
|
||||||
|
addon.uninstall();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Installs multiple add-ons specified by metadata.
|
||||||
|
*
|
||||||
|
* The first argument is an array of objects. Each object must have the
|
||||||
|
* following keys:
|
||||||
|
*
|
||||||
|
* id - public ID of the add-on to install.
|
||||||
|
* syncGUID - syncGUID for new add-on.
|
||||||
|
* enabled - boolean indicating whether the add-on should be enabled.
|
||||||
|
* requireSecureURI - Boolean indicating whether to require a secure
|
||||||
|
* URI when installing from a remote location. This defaults to
|
||||||
|
* true.
|
||||||
|
*
|
||||||
|
* The callback will be called when activity on all add-ons is complete. The
|
||||||
|
* callback receives 2 arguments, error and result.
|
||||||
|
*
|
||||||
|
* If error is truthy, it contains a string describing the overall error.
|
||||||
|
*
|
||||||
|
* The 2nd argument to the callback is always an object with details on the
|
||||||
|
* overall execution state. It contains the following keys:
|
||||||
|
*
|
||||||
|
* installedIDs Array of add-on IDs that were installed.
|
||||||
|
* installs Array of AddonInstall instances that were installed.
|
||||||
|
* addons Array of Addon instances that were installed.
|
||||||
|
* errors Array of errors encountered. Only has elements if error is
|
||||||
|
* truthy.
|
||||||
|
*
|
||||||
|
* @param installs
|
||||||
|
* Array of objects describing add-ons to install.
|
||||||
|
* @param cb
|
||||||
|
* Function to be called when all actions are complete.
|
||||||
|
*/
|
||||||
|
installAddons: function installAddons(installs, cb) {
|
||||||
|
if (!cb) {
|
||||||
|
throw new Error("Invalid argument: cb is not defined.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let ids = [];
|
||||||
|
for each (let addon in installs) {
|
||||||
|
ids.push(addon.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
AddonRepository.getAddonsByIDs(ids, {
|
||||||
|
searchSucceeded: function searchSucceeded(addons, addonsLength, total) {
|
||||||
|
this._log.info("Found " + addonsLength + "/" + ids.length +
|
||||||
|
" add-ons during repository search.");
|
||||||
|
|
||||||
|
let ourResult = {
|
||||||
|
installedIDs: [],
|
||||||
|
installs: [],
|
||||||
|
addons: [],
|
||||||
|
errors: []
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!addonsLength) {
|
||||||
|
cb(null, ourResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let expectedInstallCount = 0;
|
||||||
|
let finishedCount = 0;
|
||||||
|
let installCallback = function installCallback(error, result) {
|
||||||
|
finishedCount++;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
ourResult.errors.push(error);
|
||||||
|
} else {
|
||||||
|
ourResult.installedIDs.push(result.id);
|
||||||
|
ourResult.installs.push(result.install);
|
||||||
|
ourResult.addons.push(result.addon);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finishedCount >= expectedInstallCount) {
|
||||||
|
if (ourResult.errors.length > 0) {
|
||||||
|
cb(new Error("1 or more add-ons failed to install"), ourResult);
|
||||||
|
} else {
|
||||||
|
cb(null, ourResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
let toInstall = [];
|
||||||
|
|
||||||
|
// Rewrite the "src" query string parameter of the source URI to note
|
||||||
|
// that the add-on was installed by Sync and not something else so
|
||||||
|
// server-side metrics aren't skewed (bug 708134). The server should
|
||||||
|
// ideally send proper URLs, but this solution was deemed too
|
||||||
|
// complicated at the time the functionality was implemented.
|
||||||
|
for each (let addon in addons) {
|
||||||
|
// sourceURI presence isn't enforced by AddonRepository. So, we skip
|
||||||
|
// add-ons without a sourceURI.
|
||||||
|
if (!addon.sourceURI) {
|
||||||
|
this._log.info("Skipping install of add-on because missing " +
|
||||||
|
"sourceURI: " + addon.id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
toInstall.push(addon);
|
||||||
|
|
||||||
|
// We should always be able to QI the nsIURI to nsIURL. If not, we
|
||||||
|
// still try to install the add-on, but we don't rewrite the URL,
|
||||||
|
// potentially skewing metrics.
|
||||||
|
try {
|
||||||
|
addon.sourceURI.QueryInterface(Ci.nsIURL);
|
||||||
|
} catch (ex) {
|
||||||
|
this._log.warn("Unable to QI sourceURI to nsIURL: " +
|
||||||
|
addon.sourceURI.spec);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let params = addon.sourceURI.query.split("&").map(
|
||||||
|
function rewrite(param) {
|
||||||
|
|
||||||
|
if (param.indexOf("src=") == 0) {
|
||||||
|
return "src=sync";
|
||||||
|
} else {
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addon.sourceURI.query = params.join("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedInstallCount = toInstall.length;
|
||||||
|
|
||||||
|
if (!expectedInstallCount) {
|
||||||
|
cb(null, ourResult);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start all the installs asynchronously. They will report back to us
|
||||||
|
// as they finish, eventually triggering the global callback.
|
||||||
|
for each (let addon in toInstall) {
|
||||||
|
let options = {};
|
||||||
|
for each (let install in installs) {
|
||||||
|
if (install.id == addon.id) {
|
||||||
|
options = install;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.installAddonFromSearchResult(addon, options, installCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
}.bind(this),
|
||||||
|
|
||||||
|
searchFailed: function searchFailed() {
|
||||||
|
cb(new Error("AddonRepository search failed"), null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the user disabled flag for an add-on.
|
||||||
|
*
|
||||||
|
* The supplied callback will ba called when the operation is
|
||||||
|
* complete. If the new flag matches the existing or if the add-on
|
||||||
|
* isn't currently active, the function will fire the callback
|
||||||
|
* immediately. Else, the callback is invoked when the AddonManager
|
||||||
|
* reports the change has taken effect or has been registered.
|
||||||
|
*
|
||||||
|
* The callback receives as arguments:
|
||||||
|
*
|
||||||
|
* (Error) Encountered error during operation or null on success.
|
||||||
|
* (Addon) The add-on instance being operated on.
|
||||||
|
*
|
||||||
|
* @param addon
|
||||||
|
* (Addon) Add-on instance to operate on.
|
||||||
|
* @param value
|
||||||
|
* (bool) New value for add-on's userDisabled property.
|
||||||
|
* @param cb
|
||||||
|
* (function) Callback to be invoked on completion.
|
||||||
|
*/
|
||||||
|
updateUserDisabled: function updateUserDisabled(addon, value, cb) {
|
||||||
|
if (addon.userDisabled == value) {
|
||||||
|
cb(null, addon);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let listener = {
|
||||||
|
onEnabling: function onEnabling(wrapper, needsRestart) {
|
||||||
|
this._log.debug("onEnabling: " + wrapper.id);
|
||||||
|
if (wrapper.id != addon.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We ignore the restartless case because we'll get onEnabled shortly.
|
||||||
|
if (!needsRestart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddonManager.removeAddonListener(listener);
|
||||||
|
cb(null, wrapper);
|
||||||
|
}.bind(this),
|
||||||
|
|
||||||
|
onEnabled: function onEnabled(wrapper) {
|
||||||
|
this._log.debug("onEnabled: " + wrapper.id);
|
||||||
|
if (wrapper.id != addon.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddonManager.removeAddonListener(listener);
|
||||||
|
cb(null, wrapper);
|
||||||
|
}.bind(this),
|
||||||
|
|
||||||
|
onDisabling: function onDisabling(wrapper, needsRestart) {
|
||||||
|
this._log.debug("onDisabling: " + wrapper.id);
|
||||||
|
if (wrapper.id != addon.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needsRestart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddonManager.removeAddonListener(listener);
|
||||||
|
cb(null, wrapper);
|
||||||
|
}.bind(this),
|
||||||
|
|
||||||
|
onDisabled: function onDisabled(wrapper) {
|
||||||
|
this._log.debug("onDisabled: " + wrapper.id);
|
||||||
|
if (wrapper.id != addon.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddonManager.removeAddonListener(listener);
|
||||||
|
cb(null, wrapper);
|
||||||
|
}.bind(this),
|
||||||
|
|
||||||
|
onOperationCancelled: function onOperationCancelled(wrapper) {
|
||||||
|
this._log.debug("onOperationCancelled: " + wrapper.id);
|
||||||
|
if (wrapper.id != addon.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddonManager.removeAddonListener(listener);
|
||||||
|
cb(new Error("Operation cancelled"), wrapper);
|
||||||
|
}.bind(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
// The add-on listeners are only fired if the add-on is active. If not, the
|
||||||
|
// change is silently updated and made active when/if the add-on is active.
|
||||||
|
|
||||||
|
if (!addon.appDisabled) {
|
||||||
|
AddonManager.addAddonListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._log.info("Updating userDisabled flag: " + addon.id + " -> " + value);
|
||||||
|
addon.userDisabled = !!value;
|
||||||
|
|
||||||
|
if (!addon.appDisabled) {
|
||||||
|
cb(null, addon);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Else the listener will handle invoking the callback.
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "AddonUtils", function() {
|
||||||
|
return new AddonUtilsInternal();
|
||||||
|
});
|
@ -35,6 +35,7 @@
|
|||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||||
|
|
||||||
|
Cu.import("resource://services-sync/addonutils.js");
|
||||||
Cu.import("resource://services-sync/addonsreconciler.js");
|
Cu.import("resource://services-sync/addonsreconciler.js");
|
||||||
Cu.import("resource://services-sync/engines.js");
|
Cu.import("resource://services-sync/engines.js");
|
||||||
Cu.import("resource://services-sync/record.js");
|
Cu.import("resource://services-sync/record.js");
|
||||||
@ -291,10 +292,11 @@ AddonsStore.prototype = {
|
|||||||
*/
|
*/
|
||||||
create: function create(record) {
|
create: function create(record) {
|
||||||
let cb = Async.makeSpinningCallback();
|
let cb = Async.makeSpinningCallback();
|
||||||
this.installAddons([{
|
AddonUtils.installAddons([{
|
||||||
id: record.addonID,
|
id: record.addonID,
|
||||||
syncGUID: record.id,
|
syncGUID: record.id,
|
||||||
enabled: record.enabled
|
enabled: record.enabled,
|
||||||
|
requireSecureURI: !Svc.Prefs.get("addons.ignoreRepositoryChecking", false),
|
||||||
}], cb);
|
}], cb);
|
||||||
|
|
||||||
// This will throw if there was an error. This will get caught by the sync
|
// This will throw if there was an error. This will get caught by the sync
|
||||||
@ -332,7 +334,7 @@ AddonsStore.prototype = {
|
|||||||
|
|
||||||
this._log.info("Uninstalling add-on: " + addon.id);
|
this._log.info("Uninstalling add-on: " + addon.id);
|
||||||
let cb = Async.makeSpinningCallback();
|
let cb = Async.makeSpinningCallback();
|
||||||
this.uninstallAddon(addon, cb);
|
AddonUtils.uninstallAddon(addon, cb);
|
||||||
cb.wait();
|
cb.wait();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -607,185 +609,6 @@ AddonsStore.prototype = {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtain an AddonInstall object from an AddonSearchResult instance.
|
|
||||||
*
|
|
||||||
* The callback will be invoked with the result of the operation. The
|
|
||||||
* callback receives 2 arguments, error and result. Error will be falsy
|
|
||||||
* on success or some kind of error value otherwise. The result argument
|
|
||||||
* will be an AddonInstall on success or null on failure. It is possible
|
|
||||||
* for the error to be falsy but result to be null. This could happen if
|
|
||||||
* an install was not found.
|
|
||||||
*
|
|
||||||
* @param addon
|
|
||||||
* AddonSearchResult to obtain install from.
|
|
||||||
* @param cb
|
|
||||||
* Function to be called with result of operation.
|
|
||||||
*/
|
|
||||||
getInstallFromSearchResult: function getInstallFromSearchResult(addon, cb) {
|
|
||||||
// We should theoretically be able to obtain (and use) addon.install if
|
|
||||||
// it is available. However, the addon.sourceURI rewriting won't be
|
|
||||||
// reflected in the AddonInstall, so we can't use it. If we ever get rid
|
|
||||||
// of sourceURI rewriting, we can avoid having to reconstruct the
|
|
||||||
// AddonInstall.
|
|
||||||
this._log.debug("Obtaining install for " + addon.id);
|
|
||||||
|
|
||||||
// Verify that the source URI uses TLS. We don't allow installs from
|
|
||||||
// insecure sources for security reasons. The Addon Manager ensures that
|
|
||||||
// cert validation, etc is performed.
|
|
||||||
if (!Svc.Prefs.get("addons.ignoreRepositoryChecking", false)) {
|
|
||||||
let scheme = addon.sourceURI.scheme;
|
|
||||||
if (scheme != "https") {
|
|
||||||
cb(new Error("Insecure source URI scheme: " + scheme), addon.install);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AddonManager.getInstallForURL(
|
|
||||||
addon.sourceURI.spec,
|
|
||||||
function handleInstall(install) {
|
|
||||||
cb(null, install);
|
|
||||||
},
|
|
||||||
"application/x-xpinstall",
|
|
||||||
undefined,
|
|
||||||
addon.name,
|
|
||||||
addon.iconURL,
|
|
||||||
addon.version
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs an add-on from an AddonSearchResult instance.
|
|
||||||
*
|
|
||||||
* The options argument defines extra options to control the install.
|
|
||||||
* Recognized keys in this map are:
|
|
||||||
*
|
|
||||||
* syncGUID - Sync GUID to use for the new add-on.
|
|
||||||
* enabled - Boolean indicating whether the add-on should be enabled upon
|
|
||||||
* install.
|
|
||||||
*
|
|
||||||
* When complete it calls a callback with 2 arguments, error and result.
|
|
||||||
*
|
|
||||||
* If error is falsy, result is an object. If error is truthy, result is
|
|
||||||
* null.
|
|
||||||
*
|
|
||||||
* The result object has the following keys:
|
|
||||||
*
|
|
||||||
* id ID of add-on that was installed.
|
|
||||||
* install AddonInstall that was installed.
|
|
||||||
* addon Addon that was installed.
|
|
||||||
*
|
|
||||||
* @param addon
|
|
||||||
* AddonSearchResult to install add-on from.
|
|
||||||
* @param options
|
|
||||||
* Object with additional metadata describing how to install add-on.
|
|
||||||
* @param cb
|
|
||||||
* Function to be invoked with result of operation.
|
|
||||||
*/
|
|
||||||
installAddonFromSearchResult:
|
|
||||||
function installAddonFromSearchResult(addon, options, cb) {
|
|
||||||
this._log.info("Trying to install add-on from search result: " + addon.id);
|
|
||||||
|
|
||||||
this.getInstallFromSearchResult(addon, function(error, install) {
|
|
||||||
if (error) {
|
|
||||||
cb(error, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!install) {
|
|
||||||
cb(new Error("AddonInstall not available: " + addon.id), null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this._log.info("Installing " + addon.id);
|
|
||||||
let log = this._log;
|
|
||||||
|
|
||||||
let listener = {
|
|
||||||
onInstallStarted: function(install) {
|
|
||||||
if (!options) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.syncGUID) {
|
|
||||||
log.info("Setting syncGUID of " + install.name +": " +
|
|
||||||
options.syncGUID);
|
|
||||||
install.addon.syncGUID = options.syncGUID;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only need to change userDisabled if it is disabled because
|
|
||||||
// enabled is the default.
|
|
||||||
if ("enabled" in options && !options.enabled) {
|
|
||||||
log.info("Marking add-on as disabled for install: " +
|
|
||||||
install.name);
|
|
||||||
install.addon.userDisabled = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onInstallEnded: function(install, addon) {
|
|
||||||
install.removeListener(listener);
|
|
||||||
|
|
||||||
cb(null, {id: addon.id, install: install, addon: addon});
|
|
||||||
},
|
|
||||||
onInstallFailed: function(install) {
|
|
||||||
install.removeListener(listener);
|
|
||||||
|
|
||||||
cb(new Error("Install failed: " + install.error), null);
|
|
||||||
},
|
|
||||||
onDownloadFailed: function(install) {
|
|
||||||
install.removeListener(listener);
|
|
||||||
|
|
||||||
cb(new Error("Download failed: " + install.error), null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
install.addListener(listener);
|
|
||||||
install.install();
|
|
||||||
}
|
|
||||||
catch (ex) {
|
|
||||||
this._log.error("Error installing add-on: " + Utils.exceptionstr(ex));
|
|
||||||
cb(ex, null);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uninstalls the Addon instance and invoke a callback when it is done.
|
|
||||||
*
|
|
||||||
* @param addon
|
|
||||||
* Addon instance to uninstall.
|
|
||||||
* @param callback
|
|
||||||
* Function to be invoked when uninstall has finished. It receives a
|
|
||||||
* truthy value signifying error and the add-on which was uninstalled.
|
|
||||||
*/
|
|
||||||
uninstallAddon: function uninstallAddon(addon, callback) {
|
|
||||||
let listener = {
|
|
||||||
onUninstalling: function(uninstalling, needsRestart) {
|
|
||||||
if (addon.id != uninstalling.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We assume restartless add-ons will send the onUninstalled event
|
|
||||||
// soon.
|
|
||||||
if (!needsRestart) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For non-restartless add-ons, we issue the callback on uninstalling
|
|
||||||
// because we will likely never see the uninstalled event.
|
|
||||||
AddonManager.removeAddonListener(listener);
|
|
||||||
callback(null, addon);
|
|
||||||
},
|
|
||||||
onUninstalled: function(uninstalled) {
|
|
||||||
if (addon.id != uninstalled.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddonManager.removeAddonListener(listener);
|
|
||||||
callback(null, addon);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
AddonManager.addAddonListener(listener);
|
|
||||||
addon.uninstall();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the userDisabled flag on an add-on.
|
* Update the userDisabled flag on an add-on.
|
||||||
*
|
*
|
||||||
@ -816,232 +639,8 @@ AddonsStore.prototype = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let listener = {
|
AddonUtils.updateUserDisabled(addon, value, callback);
|
||||||
onEnabling: function onEnabling(wrapper, needsRestart) {
|
|
||||||
this._log.debug("onEnabling: " + wrapper.id);
|
|
||||||
if (wrapper.id != addon.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We ignore the restartless case because we'll get onEnabled shortly.
|
|
||||||
if (!needsRestart) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddonManager.removeAddonListener(listener);
|
|
||||||
callback(null, wrapper);
|
|
||||||
}.bind(this),
|
|
||||||
|
|
||||||
onEnabled: function onEnabled(wrapper) {
|
|
||||||
this._log.debug("onEnabled: " + wrapper.id);
|
|
||||||
if (wrapper.id != addon.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddonManager.removeAddonListener(listener);
|
|
||||||
callback(null, wrapper);
|
|
||||||
}.bind(this),
|
|
||||||
|
|
||||||
onDisabling: function onDisabling(wrapper, needsRestart) {
|
|
||||||
this._log.debug("onDisabling: " + wrapper.id);
|
|
||||||
if (wrapper.id != addon.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!needsRestart) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddonManager.removeAddonListener(listener);
|
|
||||||
callback(null, wrapper);
|
|
||||||
}.bind(this),
|
|
||||||
|
|
||||||
onDisabled: function onDisabled(wrapper) {
|
|
||||||
this._log.debug("onDisabled: " + wrapper.id);
|
|
||||||
if (wrapper.id != addon.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddonManager.removeAddonListener(listener);
|
|
||||||
callback(null, wrapper);
|
|
||||||
}.bind(this),
|
|
||||||
|
|
||||||
onOperationCancelled: function onOperationCancelled(wrapper) {
|
|
||||||
this._log.debug("onOperationCancelled: " + wrapper.id);
|
|
||||||
if (wrapper.id != addon.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddonManager.removeAddonListener(listener);
|
|
||||||
callback(new Error("Operation cancelled"), wrapper);
|
|
||||||
}.bind(this)
|
|
||||||
};
|
|
||||||
|
|
||||||
// The add-on listeners are only fired if the add-on is active. If not, the
|
|
||||||
// change is silently updated and made active when/if the add-on is active.
|
|
||||||
|
|
||||||
if (!addon.appDisabled) {
|
|
||||||
AddonManager.addAddonListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._log.info("Updating userDisabled flag: " + addon.id + " -> " + value);
|
|
||||||
addon.userDisabled = !!value;
|
|
||||||
|
|
||||||
if (!addon.appDisabled) {
|
|
||||||
callback(null, addon);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Else the listener will handle invoking the callback.
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs multiple add-ons specified by metadata.
|
|
||||||
*
|
|
||||||
* The first argument is an array of objects. Each object must have the
|
|
||||||
* following keys:
|
|
||||||
*
|
|
||||||
* id - public ID of the add-on to install.
|
|
||||||
* syncGUID - syncGUID for new add-on.
|
|
||||||
* enabled - boolean indicating whether the add-on should be enabled.
|
|
||||||
*
|
|
||||||
* The callback will be called when activity on all add-ons is complete. The
|
|
||||||
* callback receives 2 arguments, error and result.
|
|
||||||
*
|
|
||||||
* If error is truthy, it contains a string describing the overall error.
|
|
||||||
*
|
|
||||||
* The 2nd argument to the callback is always an object with details on the
|
|
||||||
* overall execution state. It contains the following keys:
|
|
||||||
*
|
|
||||||
* installedIDs Array of add-on IDs that were installed.
|
|
||||||
* installs Array of AddonInstall instances that were installed.
|
|
||||||
* addons Array of Addon instances that were installed.
|
|
||||||
* errors Array of errors encountered. Only has elements if error is
|
|
||||||
* truthy.
|
|
||||||
*
|
|
||||||
* @param installs
|
|
||||||
* Array of objects describing add-ons to install.
|
|
||||||
* @param cb
|
|
||||||
* Function to be called when all actions are complete.
|
|
||||||
*/
|
|
||||||
installAddons: function installAddons(installs, cb) {
|
|
||||||
if (!cb) {
|
|
||||||
throw new Error("Invalid argument: cb is not defined.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let ids = [];
|
|
||||||
for each (let addon in installs) {
|
|
||||||
ids.push(addon.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
AddonRepository.getAddonsByIDs(ids, {
|
|
||||||
searchSucceeded: function searchSucceeded(addons, addonsLength, total) {
|
|
||||||
this._log.info("Found " + addonsLength + "/" + ids.length +
|
|
||||||
" add-ons during repository search.");
|
|
||||||
|
|
||||||
let ourResult = {
|
|
||||||
installedIDs: [],
|
|
||||||
installs: [],
|
|
||||||
addons: [],
|
|
||||||
errors: []
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!addonsLength) {
|
|
||||||
cb(null, ourResult);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let expectedInstallCount = 0;
|
|
||||||
let finishedCount = 0;
|
|
||||||
let installCallback = function installCallback(error, result) {
|
|
||||||
finishedCount++;
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
ourResult.errors.push(error);
|
|
||||||
} else {
|
|
||||||
ourResult.installedIDs.push(result.id);
|
|
||||||
ourResult.installs.push(result.install);
|
|
||||||
ourResult.addons.push(result.addon);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finishedCount >= expectedInstallCount) {
|
|
||||||
if (ourResult.errors.length > 0) {
|
|
||||||
cb(new Error("1 or more add-ons failed to install"), ourResult);
|
|
||||||
} else {
|
|
||||||
cb(null, ourResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
let toInstall = [];
|
|
||||||
|
|
||||||
// Rewrite the "src" query string parameter of the source URI to note
|
|
||||||
// that the add-on was installed by Sync and not something else so
|
|
||||||
// server-side metrics aren't skewed (bug 708134). The server should
|
|
||||||
// ideally send proper URLs, but this solution was deemed too
|
|
||||||
// complicated at the time the functionality was implemented.
|
|
||||||
for each (let addon in addons) {
|
|
||||||
// sourceURI presence isn't enforced by AddonRepository. So, we skip
|
|
||||||
// add-ons without a sourceURI.
|
|
||||||
if (!addon.sourceURI) {
|
|
||||||
this._log.info("Skipping install of add-on because missing " +
|
|
||||||
"sourceURI: " + addon.id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
toInstall.push(addon);
|
|
||||||
|
|
||||||
// We should always be able to QI the nsIURI to nsIURL. If not, we
|
|
||||||
// still try to install the add-on, but we don't rewrite the URL,
|
|
||||||
// potentially skewing metrics.
|
|
||||||
try {
|
|
||||||
addon.sourceURI.QueryInterface(Ci.nsIURL);
|
|
||||||
} catch (ex) {
|
|
||||||
this._log.warn("Unable to QI sourceURI to nsIURL: " +
|
|
||||||
addon.sourceURI.spec);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let params = addon.sourceURI.query.split("&").map(
|
|
||||||
function rewrite(param) {
|
|
||||||
|
|
||||||
if (param.indexOf("src=") == 0) {
|
|
||||||
return "src=sync";
|
|
||||||
} else {
|
|
||||||
return param;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
addon.sourceURI.query = params.join("&");
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedInstallCount = toInstall.length;
|
|
||||||
|
|
||||||
if (!expectedInstallCount) {
|
|
||||||
cb(null, ourResult);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start all the installs asynchronously. They will report back to us
|
|
||||||
// as they finish, eventually triggering the global callback.
|
|
||||||
for each (let addon in toInstall) {
|
|
||||||
let options = {};
|
|
||||||
for each (let install in installs) {
|
|
||||||
if (install.id == addon.id) {
|
|
||||||
options = install;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.installAddonFromSearchResult(addon, options, installCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
}.bind(this),
|
|
||||||
|
|
||||||
searchFailed: function searchFailed() {
|
|
||||||
cb(new Error("AddonRepository search failed"), null);
|
|
||||||
}.bind(this)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -193,6 +193,7 @@ let SyncScheduler = {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "weave:service:setup-complete":
|
case "weave:service:setup-complete":
|
||||||
|
Services.prefs.savePrefFile(null);
|
||||||
Svc.Idle.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
|
Svc.Idle.addIdleObserver(this, Svc.Prefs.get("scheduler.idleTime"));
|
||||||
break;
|
break;
|
||||||
case "weave:service:start-over":
|
case "weave:service:start-over":
|
||||||
|
@ -54,6 +54,7 @@ pref("services.sync.log.appender.file.logOnError", true);
|
|||||||
pref("services.sync.log.appender.file.logOnSuccess", false);
|
pref("services.sync.log.appender.file.logOnSuccess", false);
|
||||||
pref("services.sync.log.appender.file.maxErrorAge", 864000); // 10 days
|
pref("services.sync.log.appender.file.maxErrorAge", 864000); // 10 days
|
||||||
pref("services.sync.log.rootLogger", "Debug");
|
pref("services.sync.log.rootLogger", "Debug");
|
||||||
|
pref("services.sync.log.logger.addonutils", "Debug");
|
||||||
pref("services.sync.log.logger.service.main", "Debug");
|
pref("services.sync.log.logger.service.main", "Debug");
|
||||||
pref("services.sync.log.logger.status", "Debug");
|
pref("services.sync.log.logger.status", "Debug");
|
||||||
pref("services.sync.log.logger.authenticator", "Debug");
|
pref("services.sync.log.logger.authenticator", "Debug");
|
||||||
|
@ -380,26 +380,3 @@ RotaryEngine.prototype = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
deepCopy: function deepCopy(thing, noSort) {
|
|
||||||
if (typeof(thing) != "object" || thing == null){
|
|
||||||
return thing;
|
|
||||||
}
|
|
||||||
let ret;
|
|
||||||
|
|
||||||
if (Array.isArray(thing)) {
|
|
||||||
ret = [];
|
|
||||||
for (let i = 0; i < thing.length; i++){
|
|
||||||
ret.push(deepCopy(thing[i], noSort));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ret = {};
|
|
||||||
let props = [p for (p in thing)];
|
|
||||||
if (!noSort){
|
|
||||||
props = props.sort();
|
|
||||||
}
|
|
||||||
props.forEach(function(k) ret[k] = deepCopy(thing[k], noSort));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
161
services/sync/tests/unit/test_addon_utils.js
Normal file
161
services/sync/tests/unit/test_addon_utils.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
Cu.import("resource://services-sync/addonutils.js");
|
||||||
|
Cu.import("resource://services-common/preferences.js");
|
||||||
|
|
||||||
|
const HTTP_PORT = 8888;
|
||||||
|
const SERVER_ADDRESS = "http://127.0.0.1:8888";
|
||||||
|
|
||||||
|
let prefs = new Preferences();
|
||||||
|
|
||||||
|
prefs.set("extensions.getAddons.get.url",
|
||||||
|
SERVER_ADDRESS + "/search/guid:%IDS%");
|
||||||
|
|
||||||
|
loadAddonTestFunctions();
|
||||||
|
startupManager();
|
||||||
|
|
||||||
|
function createAndStartHTTPServer(port=HTTP_PORT) {
|
||||||
|
try {
|
||||||
|
let server = new HttpServer();
|
||||||
|
|
||||||
|
let bootstrap1XPI = ExtensionsTestPath("/addons/test_bootstrap1_1.xpi");
|
||||||
|
|
||||||
|
server.registerFile("/search/guid:missing-sourceuri%40tests.mozilla.org",
|
||||||
|
do_get_file("missing-sourceuri.xml"));
|
||||||
|
|
||||||
|
server.registerFile("/search/guid:rewrite%40tests.mozilla.org",
|
||||||
|
do_get_file("rewrite-search.xml"));
|
||||||
|
|
||||||
|
server.start(port);
|
||||||
|
|
||||||
|
return server;
|
||||||
|
} catch (ex) {
|
||||||
|
_("Got exception starting HTTP server on port " + port);
|
||||||
|
_("Error: " + Utils.exceptionStr(ex));
|
||||||
|
do_throw(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function run_test() {
|
||||||
|
initTestLogging("Trace");
|
||||||
|
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
|
||||||
|
add_test(function test_handle_empty_source_uri() {
|
||||||
|
_("Ensure that search results without a sourceURI are properly ignored.");
|
||||||
|
|
||||||
|
let server = createAndStartHTTPServer();
|
||||||
|
|
||||||
|
const ID = "missing-sourceuri@tests.mozilla.org";
|
||||||
|
|
||||||
|
let cb = Async.makeSpinningCallback();
|
||||||
|
AddonUtils.installAddons([{id: ID, requireSecureURI: false}], cb);
|
||||||
|
let result = cb.wait();
|
||||||
|
|
||||||
|
do_check_true("installedIDs" in result);
|
||||||
|
do_check_eq(0, result.installedIDs.length);
|
||||||
|
|
||||||
|
server.stop(run_next_test);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_test(function test_ignore_untrusted_source_uris() {
|
||||||
|
_("Ensures that source URIs from insecure schemes are rejected.");
|
||||||
|
|
||||||
|
let ioService = Cc["@mozilla.org/network/io-service;1"]
|
||||||
|
.getService(Ci.nsIIOService);
|
||||||
|
|
||||||
|
const bad = ["http://example.com/foo.xpi",
|
||||||
|
"ftp://example.com/foo.xpi",
|
||||||
|
"silly://example.com/foo.xpi"];
|
||||||
|
|
||||||
|
const good = ["https://example.com/foo.xpi"];
|
||||||
|
|
||||||
|
for (let s of bad) {
|
||||||
|
let sourceURI = ioService.newURI(s, null, null);
|
||||||
|
let addon = {sourceURI: sourceURI, name: "bad", id: "bad"};
|
||||||
|
|
||||||
|
try {
|
||||||
|
let cb = Async.makeSpinningCallback();
|
||||||
|
AddonUtils.getInstallFromSearchResult(addon, cb, true);
|
||||||
|
cb.wait();
|
||||||
|
} catch (ex) {
|
||||||
|
do_check_neq(null, ex);
|
||||||
|
do_check_eq(0, ex.message.indexOf("Insecure source URI"));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should never get here if an exception is thrown.
|
||||||
|
do_check_true(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
for (let s of good) {
|
||||||
|
let sourceURI = ioService.newURI(s, null, null);
|
||||||
|
let addon = {sourceURI: sourceURI, name: "good", id: "good"};
|
||||||
|
|
||||||
|
// Despite what you might think, we don't get an error in the callback.
|
||||||
|
// The install won't work because the underlying Addon instance wasn't
|
||||||
|
// proper. But, that just results in an AddonInstall that is missing
|
||||||
|
// certain values. We really just care that the callback is being invoked
|
||||||
|
// anyway.
|
||||||
|
let callback = function onInstall(error, install) {
|
||||||
|
do_check_null(error);
|
||||||
|
do_check_neq(null, install);
|
||||||
|
do_check_eq(sourceURI.spec, install.sourceURI.spec);
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
|
||||||
|
if (count >= good.length) {
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
AddonUtils.getInstallFromSearchResult(addon, callback, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
add_test(function test_source_uri_rewrite() {
|
||||||
|
_("Ensure that a 'src=api' query string is rewritten to 'src=sync'");
|
||||||
|
|
||||||
|
// This tests for conformance with bug 708134 so server-side metrics aren't
|
||||||
|
// skewed.
|
||||||
|
|
||||||
|
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
||||||
|
|
||||||
|
// We resort to monkeypatching because of the API design.
|
||||||
|
let oldFunction = AddonUtils.__proto__.installAddonFromSearchResult;
|
||||||
|
|
||||||
|
let installCalled = false;
|
||||||
|
AddonUtils.__proto__.installAddonFromSearchResult =
|
||||||
|
function testInstallAddon(addon, metadata, cb) {
|
||||||
|
|
||||||
|
do_check_eq(SERVER_ADDRESS + "/require.xpi?src=sync",
|
||||||
|
addon.sourceURI.spec);
|
||||||
|
|
||||||
|
installCalled = true;
|
||||||
|
|
||||||
|
AddonUtils.getInstallFromSearchResult(addon, function (error, install) {
|
||||||
|
do_check_null(error);
|
||||||
|
do_check_eq(SERVER_ADDRESS + "/require.xpi?src=sync",
|
||||||
|
install.sourceURI.spec);
|
||||||
|
|
||||||
|
cb(null, {id: addon.id, addon: addon, install: install});
|
||||||
|
}, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
let server = createAndStartHTTPServer();
|
||||||
|
|
||||||
|
let installCallback = Async.makeSpinningCallback();
|
||||||
|
AddonUtils.installAddons([{id: "rewrite@tests.mozilla.org"}], installCallback);
|
||||||
|
|
||||||
|
installCallback.wait();
|
||||||
|
do_check_true(installCalled);
|
||||||
|
AddonUtils.__proto__.installAddonFromSearchResult = oldFunction;
|
||||||
|
|
||||||
|
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
||||||
|
server.stop(run_next_test);
|
||||||
|
});
|
@ -3,8 +3,9 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
Cu.import("resource://services-sync/engines/addons.js");
|
|
||||||
Cu.import("resource://services-common/preferences.js");
|
Cu.import("resource://services-common/preferences.js");
|
||||||
|
Cu.import("resource://services-sync/addonutils.js");
|
||||||
|
Cu.import("resource://services-sync/engines/addons.js");
|
||||||
|
|
||||||
const HTTP_PORT = 8888;
|
const HTTP_PORT = 8888;
|
||||||
|
|
||||||
@ -53,12 +54,6 @@ function createAndStartHTTPServer(port) {
|
|||||||
server.registerFile("/search/guid:missing-xpi%40tests.mozilla.org",
|
server.registerFile("/search/guid:missing-xpi%40tests.mozilla.org",
|
||||||
do_get_file("missing-xpi-search.xml"));
|
do_get_file("missing-xpi-search.xml"));
|
||||||
|
|
||||||
server.registerFile("/search/guid:rewrite%40tests.mozilla.org",
|
|
||||||
do_get_file("rewrite-search.xml"));
|
|
||||||
|
|
||||||
server.registerFile("/search/guid:missing-sourceuri%40tests.mozilla.org",
|
|
||||||
do_get_file("missing-sourceuri.xml"));
|
|
||||||
|
|
||||||
server.start(port);
|
server.start(port);
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
@ -409,64 +404,6 @@ add_test(function test_ignore_hotfixes() {
|
|||||||
run_next_test();
|
run_next_test();
|
||||||
});
|
});
|
||||||
|
|
||||||
add_test(function test_ignore_untrusted_source_uris() {
|
|
||||||
_("Ensures that source URIs from insecure schemes are rejected.");
|
|
||||||
|
|
||||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", false);
|
|
||||||
|
|
||||||
let ioService = Cc["@mozilla.org/network/io-service;1"]
|
|
||||||
.getService(Ci.nsIIOService);
|
|
||||||
|
|
||||||
const bad = ["http://example.com/foo.xpi",
|
|
||||||
"ftp://example.com/foo.xpi",
|
|
||||||
"silly://example.com/foo.xpi"];
|
|
||||||
|
|
||||||
const good = ["https://example.com/foo.xpi"];
|
|
||||||
|
|
||||||
for each (let s in bad) {
|
|
||||||
let sourceURI = ioService.newURI(s, null, null);
|
|
||||||
let addon = {sourceURI: sourceURI, name: "foo"};
|
|
||||||
|
|
||||||
try {
|
|
||||||
let cb = Async.makeSpinningCallback();
|
|
||||||
store.getInstallFromSearchResult(addon, cb);
|
|
||||||
cb.wait();
|
|
||||||
} catch (ex) {
|
|
||||||
do_check_neq(null, ex);
|
|
||||||
do_check_eq(0, ex.message.indexOf("Insecure source URI"));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We should never get here if an exception is thrown.
|
|
||||||
do_check_true(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
for each (let s in good) {
|
|
||||||
let sourceURI = ioService.newURI(s, null, null);
|
|
||||||
let addon = {sourceURI: sourceURI, name: "foo", id: "foo"};
|
|
||||||
|
|
||||||
// Despite what you might think, we don't get an error in the callback.
|
|
||||||
// The install won't work because the underlying Addon instance wasn't
|
|
||||||
// proper. But, that just results in an AddonInstall that is missing
|
|
||||||
// certain values. We really just care that the callback is being invoked
|
|
||||||
// anyway.
|
|
||||||
let callback = function(error, install) {
|
|
||||||
do_check_eq(null, error);
|
|
||||||
do_check_neq(null, install);
|
|
||||||
do_check_eq(sourceURI.spec, install.sourceURI.spec);
|
|
||||||
|
|
||||||
count += 1;
|
|
||||||
|
|
||||||
if (count >= good.length) {
|
|
||||||
run_next_test();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
store.getInstallFromSearchResult(addon, callback);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
add_test(function test_wipe() {
|
add_test(function test_wipe() {
|
||||||
_("Ensures that wiping causes add-ons to be uninstalled.");
|
_("Ensures that wiping causes add-ons to be uninstalled.");
|
||||||
|
|
||||||
@ -482,64 +419,3 @@ add_test(function test_wipe() {
|
|||||||
|
|
||||||
run_next_test();
|
run_next_test();
|
||||||
});
|
});
|
||||||
|
|
||||||
add_test(function test_source_uri_rewrite() {
|
|
||||||
_("Ensure that a 'src=api' query string is rewritten to 'src=sync'");
|
|
||||||
|
|
||||||
// This tests for conformance with bug 708134 so server-side metrics aren't
|
|
||||||
// skewed.
|
|
||||||
|
|
||||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
|
||||||
|
|
||||||
// We resort to monkeypatching because of the API design.
|
|
||||||
let oldFunction = store.__proto__.installAddonFromSearchResult;
|
|
||||||
|
|
||||||
let installCalled = false;
|
|
||||||
store.__proto__.installAddonFromSearchResult =
|
|
||||||
function testInstallAddon(addon, metadata, cb) {
|
|
||||||
|
|
||||||
do_check_eq("http://127.0.0.1:8888/require.xpi?src=sync",
|
|
||||||
addon.sourceURI.spec);
|
|
||||||
|
|
||||||
installCalled = true;
|
|
||||||
|
|
||||||
store.getInstallFromSearchResult(addon, function (error, install) {
|
|
||||||
do_check_eq("http://127.0.0.1:8888/require.xpi?src=sync",
|
|
||||||
install.sourceURI.spec);
|
|
||||||
|
|
||||||
cb(null, {id: addon.id, addon: addon, install: install});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
let server = createAndStartHTTPServer(HTTP_PORT);
|
|
||||||
|
|
||||||
let installCallback = Async.makeSpinningCallback();
|
|
||||||
store.installAddons([{id: "rewrite@tests.mozilla.org"}], installCallback);
|
|
||||||
|
|
||||||
installCallback.wait();
|
|
||||||
do_check_true(installCalled);
|
|
||||||
store.__proto__.installAddonFromSearchResult = oldFunction;
|
|
||||||
|
|
||||||
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
|
||||||
server.stop(run_next_test);
|
|
||||||
});
|
|
||||||
|
|
||||||
add_test(function test_handle_empty_source_uri() {
|
|
||||||
_("Ensure that search results without a sourceURI are properly ignored.");
|
|
||||||
|
|
||||||
Svc.Prefs.set("addons.ignoreRepositoryChecking", true);
|
|
||||||
|
|
||||||
let server = createAndStartHTTPServer(HTTP_PORT);
|
|
||||||
|
|
||||||
const ID = "missing-sourceuri@tests.mozilla.org";
|
|
||||||
|
|
||||||
let cb = Async.makeSpinningCallback();
|
|
||||||
store.installAddons([{id: ID}], cb);
|
|
||||||
let result = cb.wait();
|
|
||||||
|
|
||||||
do_check_true("installedIDs" in result);
|
|
||||||
do_check_eq(0, result.installedIDs.length);
|
|
||||||
|
|
||||||
Svc.Prefs.reset("addons.ignoreRepositoryChecking");
|
|
||||||
server.stop(run_next_test);
|
|
||||||
});
|
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
Cu.import("resource://services-common/log4moz.js");
|
Cu.import("resource://services-common/log4moz.js");
|
||||||
Cu.import("resource://services-sync/record.js");
|
Cu.import("resource://services-sync/record.js");
|
||||||
Cu.import("resource://services-sync/engines.js");
|
Cu.import("resource://services-sync/engines.js");
|
||||||
Cu.import("resource://services-sync/engines/bookmarks.js");
|
Cu.import("resource://services-sync/engines/bookmarks.js");
|
||||||
Cu.import("resource://services-sync/util.js");
|
Cu.import("resource://services-sync/util.js");
|
||||||
|
|
||||||
Cu.import("resource://services-sync/service.js");
|
Cu.import("resource://services-sync/service.js");
|
||||||
Cu.import("resource://gre/modules/PlacesUtils.jsm");
|
Cu.import("resource://gre/modules/PlacesUtils.jsm");
|
||||||
|
Cu.import("resource://testing-common/services-common/utils.js");
|
||||||
|
|
||||||
const DESCRIPTION_ANNO = "bookmarkProperties/description";
|
const DESCRIPTION_ANNO = "bookmarkProperties/description";
|
||||||
|
|
||||||
@ -56,7 +59,7 @@ store.wipe();
|
|||||||
function makeLivemark(p, mintGUID) {
|
function makeLivemark(p, mintGUID) {
|
||||||
let b = new Livemark("bookmarks", p.id);
|
let b = new Livemark("bookmarks", p.id);
|
||||||
// Copy here, because tests mutate the contents.
|
// Copy here, because tests mutate the contents.
|
||||||
b.cleartext = deepCopy(p);
|
b.cleartext = TestingUtils.deepCopy(p);
|
||||||
|
|
||||||
if (mintGUID)
|
if (mintGUID)
|
||||||
b.id = Utils.makeGUID();
|
b.id = Utils.makeGUID();
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
const modules = [
|
const modules = [
|
||||||
"addonsreconciler.js",
|
"addonutils.js",
|
||||||
"constants.js",
|
"addonsreconciler.js",
|
||||||
"engines/addons.js",
|
"constants.js",
|
||||||
"engines/bookmarks.js",
|
"engines/addons.js",
|
||||||
"engines/clients.js",
|
"engines/bookmarks.js",
|
||||||
"engines/forms.js",
|
"engines/clients.js",
|
||||||
"engines/history.js",
|
"engines/forms.js",
|
||||||
"engines/passwords.js",
|
"engines/history.js",
|
||||||
"engines/prefs.js",
|
"engines/passwords.js",
|
||||||
"engines/tabs.js",
|
"engines/prefs.js",
|
||||||
"engines.js",
|
"engines/tabs.js",
|
||||||
"identity.js",
|
"engines.js",
|
||||||
"jpakeclient.js",
|
"identity.js",
|
||||||
"keys.js",
|
"jpakeclient.js",
|
||||||
"main.js",
|
"keys.js",
|
||||||
"notifications.js",
|
"main.js",
|
||||||
"policies.js",
|
"notifications.js",
|
||||||
"record.js",
|
"policies.js",
|
||||||
"resource.js",
|
"record.js",
|
||||||
"rest.js",
|
"resource.js",
|
||||||
"service.js",
|
"rest.js",
|
||||||
"status.js",
|
"service.js",
|
||||||
"util.js",
|
"status.js",
|
||||||
|
"util.js",
|
||||||
];
|
];
|
||||||
|
|
||||||
function run_test() {
|
function run_test() {
|
||||||
|
@ -155,12 +155,77 @@ let quotaValue;
|
|||||||
Observers.add("weave:service:quota:remaining",
|
Observers.add("weave:service:quota:remaining",
|
||||||
function (subject) { quotaValue = subject; });
|
function (subject) { quotaValue = subject; });
|
||||||
|
|
||||||
let server;
|
|
||||||
|
|
||||||
function run_test() {
|
function run_test() {
|
||||||
logger = Log4Moz.repository.getLogger('Test');
|
logger = Log4Moz.repository.getLogger('Test');
|
||||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||||
|
|
||||||
|
Svc.Prefs.set("network.numRetries", 1); // speed up test
|
||||||
|
run_next_test();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This apparently has to come first in order for our PAC URL to be hit.
|
||||||
|
// Don't put any other HTTP requests earlier in the file!
|
||||||
|
add_test(function test_proxy_auth_redirect() {
|
||||||
|
_("Ensure that a proxy auth redirect (which switches out our channel) " +
|
||||||
|
"doesn't break AsyncResource.");
|
||||||
|
let server = httpd_setup({
|
||||||
|
"/open": server_open,
|
||||||
|
"/pac2": server_pac
|
||||||
|
});
|
||||||
|
|
||||||
|
PACSystemSettings.PACURI = "http://localhost:8080/pac2";
|
||||||
|
installFakePAC();
|
||||||
|
let res = new AsyncResource("http://localhost:8080/open");
|
||||||
|
res.get(function (error, result) {
|
||||||
|
do_check_true(!error);
|
||||||
|
do_check_true(pacFetched);
|
||||||
|
do_check_true(fetched);
|
||||||
|
do_check_eq("This path exists", result);
|
||||||
|
pacFetched = fetched = false;
|
||||||
|
uninstallFakePAC();
|
||||||
|
server.stop(run_next_test);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_test(function test_new_channel() {
|
||||||
|
_("Ensure a redirect to a new channel is handled properly.");
|
||||||
|
|
||||||
|
let resourceRequested = false;
|
||||||
|
function resourceHandler(metadata, response) {
|
||||||
|
resourceRequested = true;
|
||||||
|
|
||||||
|
let body = "Test";
|
||||||
|
response.setHeader("Content-Type", "text/plain");
|
||||||
|
response.bodyOutputStream.write(body, body.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectHandler(metadata, response) {
|
||||||
|
let body = "Redirecting";
|
||||||
|
response.setStatusLine(metadata.httpVersion, 307, "TEMPORARY REDIRECT");
|
||||||
|
response.setHeader("Location", "http://localhost:8080/resource");
|
||||||
|
response.bodyOutputStream.write(body, body.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
let server = httpd_setup({"/resource": resourceHandler,
|
||||||
|
"/redirect": redirectHandler},
|
||||||
|
8080);
|
||||||
|
|
||||||
|
let request = new AsyncResource("http://localhost:8080/redirect");
|
||||||
|
request.get(function onRequest(error, content) {
|
||||||
|
do_check_null(error);
|
||||||
|
do_check_true(resourceRequested);
|
||||||
|
do_check_eq(200, content.status);
|
||||||
|
do_check_true("content-type" in content.headers);
|
||||||
|
do_check_eq("text/plain", content.headers["content-type"]);
|
||||||
|
|
||||||
|
server.stop(run_next_test);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
let server;
|
||||||
|
|
||||||
|
add_test(function setup() {
|
||||||
server = httpd_setup({
|
server = httpd_setup({
|
||||||
"/open": server_open,
|
"/open": server_open,
|
||||||
"/protected": server_protected,
|
"/protected": server_protected,
|
||||||
@ -176,27 +241,7 @@ function run_test() {
|
|||||||
"/quota-error": server_quota_error
|
"/quota-error": server_quota_error
|
||||||
});
|
});
|
||||||
|
|
||||||
Svc.Prefs.set("network.numRetries", 1); // speed up test
|
|
||||||
run_next_test();
|
run_next_test();
|
||||||
}
|
|
||||||
|
|
||||||
// This apparently has to come first in order for our PAC URL to be hit.
|
|
||||||
// Don't put any other HTTP requests earlier in the file!
|
|
||||||
add_test(function test_proxy_auth_redirect() {
|
|
||||||
_("Ensure that a proxy auth redirect (which switches out our channel) " +
|
|
||||||
"doesn't break AsyncResource.");
|
|
||||||
PACSystemSettings.PACURI = "http://localhost:8080/pac2";
|
|
||||||
installFakePAC();
|
|
||||||
let res = new AsyncResource("http://localhost:8080/open");
|
|
||||||
res.get(function (error, result) {
|
|
||||||
do_check_true(!error);
|
|
||||||
do_check_true(pacFetched);
|
|
||||||
do_check_true(fetched);
|
|
||||||
do_check_eq("This path exists", result);
|
|
||||||
pacFetched = fetched = false;
|
|
||||||
uninstallFakePAC();
|
|
||||||
run_next_test();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
add_test(function test_members() {
|
add_test(function test_members() {
|
||||||
@ -662,38 +707,3 @@ add_test(function test_uri_construction() {
|
|||||||
add_test(function eliminate_server() {
|
add_test(function eliminate_server() {
|
||||||
server.stop(run_next_test);
|
server.stop(run_next_test);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_test(function test_new_channel() {
|
|
||||||
_("Ensure a redirect to a new channel is handled properly.");
|
|
||||||
|
|
||||||
let resourceRequested = false;
|
|
||||||
function resourceHandler(metadata, response) {
|
|
||||||
resourceRequested = true;
|
|
||||||
|
|
||||||
let body = "Test";
|
|
||||||
response.setHeader("Content-Type", "text/plain");
|
|
||||||
response.bodyOutputStream.write(body, body.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
function redirectHandler(metadata, response) {
|
|
||||||
let body = "Redirecting";
|
|
||||||
response.setStatusLine(metadata.httpVersion, 307, "TEMPORARY REDIRECT");
|
|
||||||
response.setHeader("Location", "http://localhost:8080/resource");
|
|
||||||
response.bodyOutputStream.write(body, body.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
let server = httpd_setup({"/resource": resourceHandler,
|
|
||||||
"/redirect": redirectHandler},
|
|
||||||
8080);
|
|
||||||
|
|
||||||
let request = new AsyncResource("http://localhost:8080/redirect");
|
|
||||||
request.get(function onRequest(error, content) {
|
|
||||||
do_check_null(error);
|
|
||||||
do_check_true(resourceRequested);
|
|
||||||
do_check_eq(200, content.status);
|
|
||||||
do_check_true("content-type" in content.headers);
|
|
||||||
do_check_eq("text/plain", content.headers["content-type"]);
|
|
||||||
|
|
||||||
server.stop(run_next_test);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
Cu.import("resource://services-sync/engines/tabs.js");
|
Cu.import("resource://services-sync/engines/tabs.js");
|
||||||
Cu.import("resource://services-sync/util.js");
|
Cu.import("resource://services-sync/util.js");
|
||||||
|
Cu.import("resource://testing-common/services-common/utils.js");
|
||||||
|
|
||||||
function test_lastUsed() {
|
function test_lastUsed() {
|
||||||
let store = new TabEngine()._store;
|
let store = new TabEngine()._store;
|
||||||
@ -80,7 +84,7 @@ function fakeSessionSvc(url, numtabs) {
|
|||||||
if (numtabs) {
|
if (numtabs) {
|
||||||
let tabs = obj.windows[0].tabs;
|
let tabs = obj.windows[0].tabs;
|
||||||
for (let i = 0; i < numtabs-1; i++)
|
for (let i = 0; i < numtabs-1; i++)
|
||||||
tabs.push(deepCopy(tabs[0]));
|
tabs.push(TestingUtils.deepCopy(tabs[0]));
|
||||||
}
|
}
|
||||||
return JSON.stringify(obj);
|
return JSON.stringify(obj);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ tail =
|
|||||||
|
|
||||||
# util contains a bunch of functionality used throughout.
|
# util contains a bunch of functionality used throughout.
|
||||||
[test_utils_catch.js]
|
[test_utils_catch.js]
|
||||||
[test_utils_deepCopy.js]
|
|
||||||
[test_utils_deepEquals.js]
|
[test_utils_deepEquals.js]
|
||||||
[test_utils_deferGetSet.js]
|
[test_utils_deferGetSet.js]
|
||||||
[test_utils_deriveKey.js]
|
[test_utils_deriveKey.js]
|
||||||
@ -25,6 +24,7 @@ tail =
|
|||||||
[test_utils_passphrase.js]
|
[test_utils_passphrase.js]
|
||||||
|
|
||||||
# We have a number of other libraries that are pretty much standalone.
|
# We have a number of other libraries that are pretty much standalone.
|
||||||
|
[test_addon_utils.js]
|
||||||
[test_httpd_sync_server.js]
|
[test_httpd_sync_server.js]
|
||||||
[test_jpakeclient.js]
|
[test_jpakeclient.js]
|
||||||
# Bug 618233: this test produces random failures on Windows 7.
|
# Bug 618233: this test produces random failures on Windows 7.
|
||||||
|
@ -11,7 +11,7 @@ Cu.import("resource://gre/modules/AddonManager.jsm");
|
|||||||
Cu.import("resource://gre/modules/AddonRepository.jsm");
|
Cu.import("resource://gre/modules/AddonRepository.jsm");
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
Cu.import("resource://services-common/async.js");
|
Cu.import("resource://services-common/async.js");
|
||||||
Cu.import("resource://services-sync/engines.js");
|
Cu.import("resource://services-sync/addonutils.js");
|
||||||
Cu.import("resource://services-sync/util.js");
|
Cu.import("resource://services-sync/util.js");
|
||||||
Cu.import("resource://tps/logger.jsm");
|
Cu.import("resource://tps/logger.jsm");
|
||||||
|
|
||||||
@ -59,8 +59,7 @@ Addon.prototype = {
|
|||||||
Logger.AssertTrue(!!addon, 'could not find addon ' + this.id + ' to uninstall');
|
Logger.AssertTrue(!!addon, 'could not find addon ' + this.id + ' to uninstall');
|
||||||
|
|
||||||
cb = Async.makeSpinningCallback();
|
cb = Async.makeSpinningCallback();
|
||||||
let store = Engines.get("addons")._store;
|
AddonUtils.uninstallAddon(addon, cb);
|
||||||
store.uninstallAddon(addon, cb);
|
|
||||||
cb.wait();
|
cb.wait();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -97,11 +96,7 @@ Addon.prototype = {
|
|||||||
// for the addon's install .xml; we'll read the actual id from the .xml.
|
// for the addon's install .xml; we'll read the actual id from the .xml.
|
||||||
|
|
||||||
let cb = Async.makeSpinningCallback();
|
let cb = Async.makeSpinningCallback();
|
||||||
// We call the store's APIs for installation because it is simpler. If that
|
AddonUtils.installAddons([{id: this.id, requireSecureURI: false}], cb);
|
||||||
// API is broken, it should ideally be caught by an xpcshell test. But, if
|
|
||||||
// TPS tests fail, it's all the same: a genuite reported error.
|
|
||||||
let store = Engines.get("addons")._store;
|
|
||||||
store.installAddons([{id: this.id}], cb);
|
|
||||||
let result = cb.wait();
|
let result = cb.wait();
|
||||||
|
|
||||||
Logger.AssertEqual(1, result.installedIDs.length, "Exactly 1 add-on was installed.");
|
Logger.AssertEqual(1, result.installedIDs.length, "Exactly 1 add-on was installed.");
|
||||||
@ -121,9 +116,8 @@ Addon.prototype = {
|
|||||||
throw new Error("Unknown flag to setEnabled: " + flag);
|
throw new Error("Unknown flag to setEnabled: " + flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
let store = Engines.get("addons")._store;
|
|
||||||
let cb = Async.makeSpinningCallback();
|
let cb = Async.makeSpinningCallback();
|
||||||
store.updateUserDisabled(this.addon, userDisabled, cb);
|
AddonUtils.updateUserDisabled(this.addon, userDisabled, cb);
|
||||||
cb.wait();
|
cb.wait();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -63,6 +63,7 @@ _SERV_FILES = \
|
|||||||
harness.xul \
|
harness.xul \
|
||||||
browser-test-overlay.xul \
|
browser-test-overlay.xul \
|
||||||
browser-test.js \
|
browser-test.js \
|
||||||
|
cc-analyzer.js \
|
||||||
chrome-harness.js \
|
chrome-harness.js \
|
||||||
browser-harness.xul \
|
browser-harness.xul \
|
||||||
redirect.html \
|
redirect.html \
|
||||||
|
@ -8,4 +8,5 @@
|
|||||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||||
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"/>
|
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"/>
|
||||||
<script type="application/javascript" src="chrome://mochikit/content/browser-test.js"/>
|
<script type="application/javascript" src="chrome://mochikit/content/browser-test.js"/>
|
||||||
|
<script type="application/javascript" src="chrome://mochikit/content/cc-analyzer.js"/>
|
||||||
</overlay>
|
</overlay>
|
||||||
|
@ -7,6 +7,12 @@ if (Cc === undefined) {
|
|||||||
var Ci = Components.interfaces;
|
var Ci = Components.interfaces;
|
||||||
var Cu = Components.utils;
|
var Cu = Components.utils;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||||
|
"resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
window.addEventListener("load", testOnLoad, false);
|
window.addEventListener("load", testOnLoad, false);
|
||||||
|
|
||||||
function testOnLoad() {
|
function testOnLoad() {
|
||||||
@ -15,21 +21,18 @@ function testOnLoad() {
|
|||||||
gConfig = readConfig();
|
gConfig = readConfig();
|
||||||
if (gConfig.testRoot == "browser" || gConfig.testRoot == "webapprtChrome") {
|
if (gConfig.testRoot == "browser" || gConfig.testRoot == "webapprtChrome") {
|
||||||
// Make sure to launch the test harness for the first opened window only
|
// Make sure to launch the test harness for the first opened window only
|
||||||
var prefs = Cc["@mozilla.org/preferences-service;1"].
|
var prefs = Services.prefs;
|
||||||
getService(Ci.nsIPrefBranch);
|
|
||||||
if (prefs.prefHasUserValue("testing.browserTestHarness.running"))
|
if (prefs.prefHasUserValue("testing.browserTestHarness.running"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
prefs.setBoolPref("testing.browserTestHarness.running", true);
|
prefs.setBoolPref("testing.browserTestHarness.running", true);
|
||||||
|
|
||||||
var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
|
|
||||||
getService(Ci.nsIWindowWatcher);
|
|
||||||
var sstring = Cc["@mozilla.org/supports-string;1"].
|
var sstring = Cc["@mozilla.org/supports-string;1"].
|
||||||
createInstance(Ci.nsISupportsString);
|
createInstance(Ci.nsISupportsString);
|
||||||
sstring.data = location.search;
|
sstring.data = location.search;
|
||||||
|
|
||||||
ww.openWindow(window, "chrome://mochikit/content/browser-harness.xul", "browserTest",
|
Services.ww.openWindow(window, "chrome://mochikit/content/browser-harness.xul", "browserTest",
|
||||||
"chrome,centerscreen,dialog=no,resizable,titlebar,toolbar=no,width=800,height=600", sstring);
|
"chrome,centerscreen,dialog=no,resizable,titlebar,toolbar=no,width=800,height=600", sstring);
|
||||||
} else {
|
} else {
|
||||||
// This code allows us to redirect without requiring specialpowers for chrome and a11y tests.
|
// This code allows us to redirect without requiring specialpowers for chrome and a11y tests.
|
||||||
function messageHandler(m) {
|
function messageHandler(m) {
|
||||||
@ -53,15 +56,10 @@ function Tester(aTests, aDumper, aCallback) {
|
|||||||
this.dumper = aDumper;
|
this.dumper = aDumper;
|
||||||
this.tests = aTests;
|
this.tests = aTests;
|
||||||
this.callback = aCallback;
|
this.callback = aCallback;
|
||||||
this._cs = Cc["@mozilla.org/consoleservice;1"].
|
this.openedWindows = {};
|
||||||
getService(Ci.nsIConsoleService);
|
this.openedURLs = {};
|
||||||
this._wm = Cc["@mozilla.org/appshell/window-mediator;1"].
|
|
||||||
getService(Ci.nsIWindowMediator);
|
|
||||||
this._fm = Cc["@mozilla.org/focus-manager;1"].
|
|
||||||
getService(Ci.nsIFocusManager);
|
|
||||||
|
|
||||||
this._scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
|
this._scriptLoader = Services.scriptloader;
|
||||||
getService(Ci.mozIJSSubScriptLoader);
|
|
||||||
this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", this.EventUtils);
|
this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", this.EventUtils);
|
||||||
var simpleTestScope = {};
|
var simpleTestScope = {};
|
||||||
this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js", simpleTestScope);
|
this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js", simpleTestScope);
|
||||||
@ -79,6 +77,8 @@ Tester.prototype = {
|
|||||||
checker: null,
|
checker: null,
|
||||||
currentTestIndex: -1,
|
currentTestIndex: -1,
|
||||||
lastStartTime: null,
|
lastStartTime: null,
|
||||||
|
openedWindows: null,
|
||||||
|
|
||||||
get currentTest() {
|
get currentTest() {
|
||||||
return this.tests[this.currentTestIndex];
|
return this.tests[this.currentTestIndex];
|
||||||
},
|
},
|
||||||
@ -92,7 +92,9 @@ Tester.prototype = {
|
|||||||
gConfig = readConfig();
|
gConfig = readConfig();
|
||||||
this.repeat = gConfig.repeat;
|
this.repeat = gConfig.repeat;
|
||||||
this.dumper.dump("*** Start BrowserChrome Test Results ***\n");
|
this.dumper.dump("*** Start BrowserChrome Test Results ***\n");
|
||||||
this._cs.registerListener(this);
|
Services.console.registerListener(this);
|
||||||
|
Services.obs.addObserver(this, "chrome-document-global-created", false);
|
||||||
|
Services.obs.addObserver(this, "content-document-global-created", false);
|
||||||
this._globalProperties = Object.keys(window);
|
this._globalProperties = Object.keys(window);
|
||||||
this._globalPropertyWhitelist = ["navigator", "constructor", "Application",
|
this._globalPropertyWhitelist = ["navigator", "constructor", "Application",
|
||||||
"__SS_tabsToRestore", "__SSi", "webConsoleCommandController",
|
"__SS_tabsToRestore", "__SSi", "webConsoleCommandController",
|
||||||
@ -124,7 +126,7 @@ Tester.prototype = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.dumper.dump("TEST-INFO | checking window state\n");
|
this.dumper.dump("TEST-INFO | checking window state\n");
|
||||||
let windowsEnum = this._wm.getEnumerator(null);
|
let windowsEnum = Services.wm.getEnumerator(null);
|
||||||
while (windowsEnum.hasMoreElements()) {
|
while (windowsEnum.hasMoreElements()) {
|
||||||
let win = windowsEnum.getNext();
|
let win = windowsEnum.getNext();
|
||||||
if (win != window && !win.closed &&
|
if (win != window && !win.closed &&
|
||||||
@ -159,7 +161,9 @@ Tester.prototype = {
|
|||||||
this.nextTest();
|
this.nextTest();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
this._cs.unregisterListener(this);
|
Services.console.unregisterListener(this);
|
||||||
|
Services.obs.removeObserver(this, "chrome-document-global-created");
|
||||||
|
Services.obs.removeObserver(this, "content-document-global-created");
|
||||||
|
|
||||||
this.dumper.dump("\nINFO TEST-START | Shutdown\n");
|
this.dumper.dump("\nINFO TEST-START | Shutdown\n");
|
||||||
if (this.tests.length) {
|
if (this.tests.length) {
|
||||||
@ -186,10 +190,34 @@ Tester.prototype = {
|
|||||||
this.callback(this.tests);
|
this.callback(this.tests);
|
||||||
this.callback = null;
|
this.callback = null;
|
||||||
this.tests = null;
|
this.tests = null;
|
||||||
|
this.openedWindows = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
observe: function Tester_observe(aConsoleMessage) {
|
observe: function Tester_observe(aSubject, aTopic, aData) {
|
||||||
|
if (!aTopic) {
|
||||||
|
this.onConsoleMessage(aSubject);
|
||||||
|
} else if (this.currentTest) {
|
||||||
|
this.onDocumentCreated(aSubject);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDocumentCreated: function Tester_onDocumentCreated(aWindow) {
|
||||||
|
let utils = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDOMWindowUtils);
|
||||||
|
let outerID = utils.outerWindowID;
|
||||||
|
let innerID = utils.currentInnerWindowID;
|
||||||
|
|
||||||
|
if (!(outerID in this.openedWindows)) {
|
||||||
|
this.openedWindows[outerID] = this.currentTest;
|
||||||
|
}
|
||||||
|
this.openedWindows[innerID] = this.currentTest;
|
||||||
|
|
||||||
|
let url = aWindow.location.href || "about:blank";
|
||||||
|
this.openedURLs[outerID] = this.openedURLs[innerID] = url;
|
||||||
|
},
|
||||||
|
|
||||||
|
onConsoleMessage: function Tester_onConsoleMessage(aConsoleMessage) {
|
||||||
// Ignore empty messages.
|
// Ignore empty messages.
|
||||||
if (!aConsoleMessage.message)
|
if (!aConsoleMessage.message)
|
||||||
return;
|
return;
|
||||||
@ -265,12 +293,20 @@ Tester.prototype = {
|
|||||||
// Schedule GC and CC runs before finishing in order to detect
|
// Schedule GC and CC runs before finishing in order to detect
|
||||||
// DOM windows leaked by our tests or the tested code.
|
// DOM windows leaked by our tests or the tested code.
|
||||||
Cu.schedulePreciseGC((function () {
|
Cu.schedulePreciseGC((function () {
|
||||||
let winutils = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
let analyzer = new CCAnalyzer();
|
||||||
.getInterface(Ci.nsIDOMWindowUtils);
|
analyzer.run(function () {
|
||||||
winutils.garbageCollect();
|
for (let obj of analyzer.find("nsGlobalWindow ")) {
|
||||||
winutils.garbageCollect();
|
let m = obj.name.match(/^nsGlobalWindow #(\d+)/);
|
||||||
winutils.garbageCollect();
|
if (m && m[1] in this.openedWindows) {
|
||||||
this.finish();
|
let test = this.openedWindows[m[1]];
|
||||||
|
let msg = "leaked until shutdown [" + obj.name +
|
||||||
|
" " + (this.openedURLs[m[1]] || "NULL") + "]";
|
||||||
|
test.addResult(new testResult(false, msg, "", false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.finish();
|
||||||
|
}.bind(this));
|
||||||
}).bind(this));
|
}).bind(this));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -462,9 +498,7 @@ function testScope(aTester, aTest) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.executeSoon = function test_executeSoon(func) {
|
this.executeSoon = function test_executeSoon(func) {
|
||||||
let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
|
Services.tm.mainThread.dispatch({
|
||||||
|
|
||||||
tm.mainThread.dispatch({
|
|
||||||
run: function() {
|
run: function() {
|
||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
|
126
testing/mochitest/cc-analyzer.js
Normal file
126
testing/mochitest/cc-analyzer.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
function CCAnalyzer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
CCAnalyzer.prototype = {
|
||||||
|
clear: function () {
|
||||||
|
this.callback = null;
|
||||||
|
this.processingCount = 0;
|
||||||
|
this.graph = {};
|
||||||
|
this.roots = [];
|
||||||
|
this.garbage = [];
|
||||||
|
this.edges = [];
|
||||||
|
this.listener = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
run: function (aCallback) {
|
||||||
|
this.clear();
|
||||||
|
this.callback = aCallback;
|
||||||
|
|
||||||
|
this.listener = Cc["@mozilla.org/cycle-collector-logger;1"].
|
||||||
|
createInstance(Ci.nsICycleCollectorListener);
|
||||||
|
|
||||||
|
this.listener.disableLog = true;
|
||||||
|
this.listener.wantAfterProcessing = true;
|
||||||
|
|
||||||
|
this.runCC(3);
|
||||||
|
},
|
||||||
|
|
||||||
|
runCC: function (aCounter) {
|
||||||
|
let utils = window.QueryInterface(Ci.nsIInterfaceRequestor).
|
||||||
|
getInterface(Ci.nsIDOMWindowUtils);
|
||||||
|
|
||||||
|
if (aCounter > 1) {
|
||||||
|
utils.garbageCollect();
|
||||||
|
setTimeout(this.runCC.bind(this, aCounter - 1), 0);
|
||||||
|
} else {
|
||||||
|
utils.garbageCollect(this.listener);
|
||||||
|
this.processLog();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
processLog: function () {
|
||||||
|
// Process entire heap step by step in 5K chunks
|
||||||
|
for (let i = 0; i < 5000; i++) {
|
||||||
|
if (!this.listener.processNext(this)) {
|
||||||
|
this.callback();
|
||||||
|
this.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next chunk on timeout.
|
||||||
|
setTimeout(this.processLog.bind(this), 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
noteRefCountedObject: function (aAddress, aRefCount, aObjectDescription) {
|
||||||
|
let o = this.ensureObject(aAddress);
|
||||||
|
o.address = aAddress;
|
||||||
|
o.refcount = aRefCount;
|
||||||
|
o.name = aObjectDescription;
|
||||||
|
},
|
||||||
|
|
||||||
|
noteGCedObject: function (aAddress, aMarked, aObjectDescription) {
|
||||||
|
let o = this.ensureObject(aAddress);
|
||||||
|
o.address = aAddress;
|
||||||
|
o.gcmarked = aMarked;
|
||||||
|
o.name = aObjectDescription;
|
||||||
|
},
|
||||||
|
|
||||||
|
noteEdge: function (aFromAddress, aToAddress, aEdgeName) {
|
||||||
|
let fromObject = this.ensureObject(aFromAddress);
|
||||||
|
let toObject = this.ensureObject(aToAddress);
|
||||||
|
fromObject.edges.push({name: aEdgeName, to: toObject});
|
||||||
|
toObject.owners.push({name: aEdgeName, from: fromObject});
|
||||||
|
|
||||||
|
this.edges.push({
|
||||||
|
name: aEdgeName,
|
||||||
|
from: fromObject,
|
||||||
|
to: toObject
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
describeRoot: function (aAddress, aKnownEdges) {
|
||||||
|
let o = this.ensureObject(aAddress);
|
||||||
|
o.root = true;
|
||||||
|
o.knownEdges = aKnownEdges;
|
||||||
|
this.roots.push(o);
|
||||||
|
},
|
||||||
|
|
||||||
|
describeGarbage: function (aAddress) {
|
||||||
|
let o = this.ensureObject(aAddress);
|
||||||
|
o.garbage = true;
|
||||||
|
this.garbage.push(o);
|
||||||
|
},
|
||||||
|
|
||||||
|
ensureObject: function (aAddress) {
|
||||||
|
if (!this.graph[aAddress])
|
||||||
|
this.graph[aAddress] = new CCObject();
|
||||||
|
|
||||||
|
return this.graph[aAddress];
|
||||||
|
},
|
||||||
|
|
||||||
|
find: function (aText) {
|
||||||
|
let result = [];
|
||||||
|
for each (let o in this.graph) {
|
||||||
|
if (!o.garbage && o.name.indexOf(aText) >= 0)
|
||||||
|
result.push(o);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function CCObject() {
|
||||||
|
this.name = "";
|
||||||
|
this.address = null;
|
||||||
|
this.refcount = 0;
|
||||||
|
this.gcmarked = false;
|
||||||
|
this.root = false;
|
||||||
|
this.garbage = false;
|
||||||
|
this.knownEdges = 0;
|
||||||
|
this.edges = [];
|
||||||
|
this.owners = [];
|
||||||
|
}
|
@ -3,6 +3,7 @@ mochikit.jar:
|
|||||||
content/browser-harness.xul (browser-harness.xul)
|
content/browser-harness.xul (browser-harness.xul)
|
||||||
content/browser-test.js (browser-test.js)
|
content/browser-test.js (browser-test.js)
|
||||||
content/browser-test-overlay.xul (browser-test-overlay.xul)
|
content/browser-test-overlay.xul (browser-test-overlay.xul)
|
||||||
|
content/cc-analyzer.js (cc-analyzer.js)
|
||||||
content/chrome-harness.js (chrome-harness.js)
|
content/chrome-harness.js (chrome-harness.js)
|
||||||
content/harness-overlay.xul (harness-overlay.xul)
|
content/harness-overlay.xul (harness-overlay.xul)
|
||||||
content/harness.xul (harness.xul)
|
content/harness.xul (harness.xul)
|
||||||
|
@ -679,14 +679,6 @@ class Mochitest(object):
|
|||||||
else:
|
else:
|
||||||
timeout = 330.0 # default JS harness timeout is 300 seconds
|
timeout = 330.0 # default JS harness timeout is 300 seconds
|
||||||
|
|
||||||
# it's a debug build, we can parse leaked DOMWindows and docShells
|
|
||||||
# but skip for WebappRT chrome tests, where DOMWindow "leaks" aren't
|
|
||||||
# meaningful. See https://bugzilla.mozilla.org/show_bug.cgi?id=733631#c46
|
|
||||||
if Automation.IS_DEBUG_BUILD and not options.webapprtChrome:
|
|
||||||
logger = ShutdownLeakLogger(self.automation.log)
|
|
||||||
else:
|
|
||||||
logger = None
|
|
||||||
|
|
||||||
if options.vmwareRecording:
|
if options.vmwareRecording:
|
||||||
self.startVMwareRecording(options);
|
self.startVMwareRecording(options);
|
||||||
|
|
||||||
@ -700,7 +692,6 @@ class Mochitest(object):
|
|||||||
certPath=options.certPath,
|
certPath=options.certPath,
|
||||||
debuggerInfo=debuggerInfo,
|
debuggerInfo=debuggerInfo,
|
||||||
symbolsPath=options.symbolsPath,
|
symbolsPath=options.symbolsPath,
|
||||||
logger = logger,
|
|
||||||
timeout = timeout)
|
timeout = timeout)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.automation.log.info("INFO | runtests.py | Received keyboard interrupt.\n");
|
self.automation.log.info("INFO | runtests.py | Received keyboard interrupt.\n");
|
||||||
@ -716,9 +707,6 @@ class Mochitest(object):
|
|||||||
self.stopWebSocketServer(options)
|
self.stopWebSocketServer(options)
|
||||||
processLeakLog(self.leak_report_file, options.leakThreshold)
|
processLeakLog(self.leak_report_file, options.leakThreshold)
|
||||||
|
|
||||||
if logger:
|
|
||||||
logger.parse()
|
|
||||||
|
|
||||||
self.automation.log.info("\nINFO | runtests.py | Running tests: end.")
|
self.automation.log.info("\nINFO | runtests.py | Running tests: end.")
|
||||||
|
|
||||||
if manifest is not None:
|
if manifest is not None:
|
||||||
|
@ -308,7 +308,9 @@ BrowserTabActor.prototype = {
|
|||||||
// Watch for globals being created in this tab.
|
// Watch for globals being created in this tab.
|
||||||
this.browser.addEventListener("DOMWindowCreated", this._onWindowCreated, true);
|
this.browser.addEventListener("DOMWindowCreated", this._onWindowCreated, true);
|
||||||
this.browser.addEventListener("pageshow", this._onWindowCreated, true);
|
this.browser.addEventListener("pageshow", this._onWindowCreated, true);
|
||||||
this._progressListener = new DebuggerProgressListener(this);
|
if (this._tabbrowser) {
|
||||||
|
this._progressListener = new DebuggerProgressListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
this._attached = true;
|
this._attached = true;
|
||||||
},
|
},
|
||||||
|
@ -2031,9 +2031,9 @@ var XPIProvider = {
|
|||||||
|
|
||||||
// We'll be replacing a currently active bootstrapped add-on so
|
// We'll be replacing a currently active bootstrapped add-on so
|
||||||
// call its uninstall method
|
// call its uninstall method
|
||||||
let oldVersion = aManifests[aLocation.name][id].version;
|
let newVersion = aManifests[aLocation.name][id].version;
|
||||||
let newVersion = oldBootstrap.version;
|
let oldVersion = oldBootstrap.version;
|
||||||
let uninstallReason = Services.vc.compare(newVersion, oldVersion) < 0 ?
|
let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ?
|
||||||
BOOTSTRAP_REASONS.ADDON_UPGRADE :
|
BOOTSTRAP_REASONS.ADDON_UPGRADE :
|
||||||
BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
|
BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user