This commit is contained in:
Ryan VanderMeulen 2014-03-20 16:49:14 -04:00
Родитель 490ae21fca b95d18d5eb
Коммит a5e0e0ab4a
41 изменённых файлов: 593 добавлений и 176 удалений

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

@ -1138,7 +1138,7 @@ let RemoteDebugger = {
*/ */
DebuggerServer.createRootActor = function createRootActor(connection) DebuggerServer.createRootActor = function createRootActor(connection)
{ {
let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise; let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
let parameters = { let parameters = {
// We do not expose browser tab actors yet, // We do not expose browser tab actors yet,
// but we still have to define tabList.getList(), // but we still have to define tabList.getList(),

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

@ -6,7 +6,7 @@
const { Cc, Ci, Cu } = require("chrome"); const { Cc, Ci, Cu } = require("chrome");
const { SimulatorProcess } = require("./simulator-process"); const { SimulatorProcess } = require("./simulator-process");
const Promise = require("sdk/core/promise"); const { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const Self = require("sdk/self"); const Self = require("sdk/self");
const System = require("sdk/system"); const System = require("sdk/system");
const { Simulator } = Cu.import("resource://gre/modules/devtools/Simulator.jsm"); const { Simulator } = Cu.import("resource://gre/modules/devtools/Simulator.jsm");

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

@ -17,7 +17,7 @@ const Runtime = require("sdk/system/runtime");
const Self = require("sdk/self"); const Self = require("sdk/self");
const URL = require("sdk/url"); const URL = require("sdk/url");
const Subprocess = require("subprocess"); const Subprocess = require("subprocess");
const Promise = require("sdk/core/promise"); const { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const { rootURI: ROOT_URI } = require('@loader/options'); const { rootURI: ROOT_URI } = require('@loader/options');
const PROFILE_URL = ROOT_URI + "profile/"; const PROFILE_URL = ROOT_URI + "profile/";

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

@ -177,6 +177,7 @@ SocialUI = {
SocialShare.populateProviderMenu(); SocialShare.populateProviderMenu();
SocialStatus.populateToolbarPalette(); SocialStatus.populateToolbarPalette();
SocialMarks.populateToolbarPalette(); SocialMarks.populateToolbarPalette();
SocialShare.update();
}, },
// This handles "ActivateSocialFeature" events fired against content documents // This handles "ActivateSocialFeature" events fired against content documents
@ -514,7 +515,7 @@ SocialShare = {
if (!provider) if (!provider)
provider = SocialSidebar.provider; provider = SocialSidebar.provider;
// if our provider has no shareURL, select the first one that does // if our provider has no shareURL, select the first one that does
if (provider && !provider.shareURL) { if (!provider || !provider.shareURL) {
let providers = [p for (p of Social.providers) if (p.shareURL)]; let providers = [p for (p of Social.providers) if (p.shareURL)];
provider = providers.length > 0 && providers[0]; provider = providers.length > 0 && providers[0];
} }
@ -582,7 +583,10 @@ SocialShare = {
// also update the relevent command's disabled state so the keyboard // also update the relevent command's disabled state so the keyboard
// shortcut only works when available. // shortcut only works when available.
let cmd = document.getElementById("Social:SharePage"); let cmd = document.getElementById("Social:SharePage");
cmd.setAttribute("disabled", shareButton.disabled ? "true" : "false"); if (shareButton.disabled)
cmd.setAttribute("disabled", "true");
else
cmd.removeAttribute("disabled");
}, },
onShowing: function() { onShowing: function() {
@ -1411,6 +1415,7 @@ SocialMarks = {
for (let cfg of contextMenus) { for (let cfg of contextMenus) {
this._populateContextPopup(cfg, providers); this._populateContextPopup(cfg, providers);
} }
this.updatePanelButtons();
}, },
MENU_LIMIT: 3, // adjustable for testing MENU_LIMIT: 3, // adjustable for testing

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

@ -1,20 +1,20 @@
let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
let baseURL = "https://example.com/browser/browser/base/content/test/social/"; let baseURL = "https://example.com/browser/browser/base/content/test/social/";
let manifest = { // normal provider
name: "provider 1",
origin: "https://example.com",
workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png",
shareURL: "https://example.com/browser/browser/base/content/test/social/share.html"
};
function test() { function test() {
waitForExplicitFinish(); waitForExplicitFinish();
let manifest = { // normal provider runSocialTests(tests);
name: "provider 1",
origin: "https://example.com",
sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png",
shareURL: "https://example.com/browser/browser/base/content/test/social/share.html"
};
runSocialTestWithProvider(manifest, function (finishcb) {
runSocialTests(tests, undefined, undefined, finishcb);
});
} }
let corpus = [ let corpus = [
@ -78,7 +78,7 @@ function loadURLInTab(url, callback) {
tab.linkedBrowser.addEventListener("load", function listener() { tab.linkedBrowser.addEventListener("load", function listener() {
is(tab.linkedBrowser.currentURI.spec, url, "tab loaded") is(tab.linkedBrowser.currentURI.spec, url, "tab loaded")
tab.linkedBrowser.removeEventListener("load", listener, true); tab.linkedBrowser.removeEventListener("load", listener, true);
callback(tab); executeSoon(function() { callback(tab) });
}, true); }, true);
} }
@ -101,10 +101,46 @@ function hasoptions(testOptions, options) {
} }
var tests = { var tests = {
testShareDisabledOnActivation: function(next) {
// starting on about:blank page, share should be visible but disabled when
// adding provider
is(gBrowser.contentDocument.location.href, "about:blank");
SocialService.addProvider(manifest, function(provider) {
is(SocialUI.enabled, true, "SocialUI is enabled");
checkSocialUI();
// share should not be enabled since we only have about:blank page
let shareButton = SocialShare.shareButton;
is(shareButton.disabled, true, "share button is disabled");
// verify the attribute for proper css
is(shareButton.getAttribute("disabled"), "true", "share button attribute is disabled");
// button should be visible
is(shareButton.hidden, false, "share button is visible");
SocialService.removeProvider(manifest.origin, next);
});
},
testShareEnabledOnActivation: function(next) {
// starting from *some* page, share should be visible and enabled when
// activating provider
let testData = corpus[0];
loadURLInTab(testData.url, function(tab) {
SocialService.addProvider(manifest, function(provider) {
is(SocialUI.enabled, true, "SocialUI is enabled");
checkSocialUI();
// share should not be enabled since we only have about:blank page
let shareButton = SocialShare.shareButton;
is(shareButton.disabled, false, "share button is enabled");
// verify the attribute for proper css
ok(!shareButton.hasAttribute("disabled"), "share button is enabled");
// button should be visible
is(shareButton.hidden, false, "share button is visible");
gBrowser.removeTab(tab);
next();
});
});
},
testSharePage: function(next) { testSharePage: function(next) {
let panel = document.getElementById("social-flyout-panel"); let provider = Social._getProviderFromOrigin(manifest.origin);
SocialSidebar.show(); let port = provider.getWorkerPort();
let port = SocialSidebar.provider.getWorkerPort();
ok(port, "provider has a port"); ok(port, "provider has a port");
let testTab; let testTab;
let testIndex = 0; let testIndex = 0;
@ -120,22 +156,19 @@ var tests = {
port.onmessage = function (e) { port.onmessage = function (e) {
let topic = e.data.topic; let topic = e.data.topic;
switch (topic) { switch (topic) {
case "got-sidebar-message":
// open a tab with share data, then open the share panel
runOneTest();
break;
case "got-share-data-message": case "got-share-data-message":
gBrowser.removeTab(testTab); gBrowser.removeTab(testTab);
hasoptions(testData.options, e.data.result); hasoptions(testData.options, e.data.result);
testData = corpus[testIndex++]; testData = corpus[testIndex++];
if (testData) { if (testData) {
runOneTest(); executeSoon(runOneTest);
} else { } else {
next(); SocialService.removeProvider(manifest.origin, next);
} }
break; break;
} }
} }
port.postMessage({topic: "test-init"}); port.postMessage({topic: "test-init"});
executeSoon(runOneTest);
} }
} }

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

@ -70,6 +70,28 @@ function test() {
} }
var tests = { var tests = {
testButtonDisabledOnActivate: function(next) {
// starting on about:blank page, share should be visible but disabled when
// adding provider
is(gBrowser.contentDocument.location.href, "about:blank");
SocialService.addProvider(manifest2, function(provider) {
is(provider.origin, manifest2.origin, "provider is installed");
let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
let widget = CustomizableUI.getWidget(id).forWindow(window)
ok(widget.node, "button added to widget set");
// bypass widget go directly to dom, check attribute states
let button = document.getElementById(id);
is(button.disabled, true, "mark button is disabled");
// verify the attribute for proper css
is(button.getAttribute("disabled"), "true", "mark button attribute is disabled");
// button should be visible
is(button.hidden, false, "mark button is visible");
checkSocialUI(window);
SocialService.removeProvider(manifest2.origin, next);
});
},
testNoButtonOnEnable: function(next) { testNoButtonOnEnable: function(next) {
// we expect the addon install dialog to appear, we need to accept the // we expect the addon install dialog to appear, we need to accept the
// install from the dialog. // install from the dialog.
@ -117,6 +139,15 @@ var tests = {
let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin); let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
let widget = CustomizableUI.getWidget(id).forWindow(window) let widget = CustomizableUI.getWidget(id).forWindow(window)
ok(widget.node, "button added to widget set"); ok(widget.node, "button added to widget set");
// bypass widget go directly to dom, check attribute states
let button = document.getElementById(id);
is(button.disabled, false, "mark button is disabled");
// verify the attribute for proper css
ok(!button.hasAttribute("disabled"), "mark button attribute is disabled");
// button should be visible
is(button.hidden, false, "mark button is visible");
checkSocialUI(window); checkSocialUI(window);
gBrowser.removeTab(tab); gBrowser.removeTab(tab);
next(); next();

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

@ -207,6 +207,7 @@ function checkSocialUI(win) {
let enabled = win.SocialUI.enabled; let enabled = win.SocialUI.enabled;
let active = Social.providers.length > 0 && !win.SocialUI._chromeless && let active = Social.providers.length > 0 && !win.SocialUI._chromeless &&
!PrivateBrowsingUtils.isWindowPrivate(win); !PrivateBrowsingUtils.isWindowPrivate(win);
let sidebarEnabled = win.SocialSidebar.provider ? enabled : false;
// if we have enabled providers, we should also have instances of those // if we have enabled providers, we should also have instances of those
// providers // providers
@ -235,7 +236,7 @@ function checkSocialUI(win) {
function isbool(a, b, msg) { function isbool(a, b, msg) {
_is(!!a, !!b, msg); _is(!!a, !!b, msg);
} }
isbool(win.SocialSidebar.canShow, enabled, "social sidebar active?"); isbool(win.SocialSidebar.canShow, sidebarEnabled, "social sidebar active?");
isbool(win.SocialChatBar.isAvailable, enabled, "chatbar available?"); isbool(win.SocialChatBar.isAvailable, enabled, "chatbar available?");
isbool(!win.SocialChatBar.chatbar.hidden, enabled, "chatbar visible?"); isbool(!win.SocialChatBar.chatbar.hidden, enabled, "chatbar visible?");
@ -276,7 +277,7 @@ function checkSocialUI(win) {
} }
// and for good measure, check all the social commands. // and for good measure, check all the social commands.
isbool(!doc.getElementById("Social:ToggleSidebar").hidden, enabled, "Social:ToggleSidebar visible?"); isbool(!doc.getElementById("Social:ToggleSidebar").hidden, sidebarEnabled, "Social:ToggleSidebar visible?");
isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Social:ToggleNotifications visible?"); isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Social:ToggleNotifications visible?");
isbool(!doc.getElementById("Social:FocusChat").hidden, enabled, "Social:FocusChat visible?"); isbool(!doc.getElementById("Social:FocusChat").hidden, enabled, "Social:FocusChat visible?");
isbool(doc.getElementById("Social:FocusChat").getAttribute("disabled"), enabled ? "false" : "true", "Social:FocusChat disabled?"); isbool(doc.getElementById("Social:FocusChat").getAttribute("disabled"), enabled ? "false" : "true", "Social:FocusChat disabled?");

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

@ -12,11 +12,20 @@ openUILinkIn = (aUrl, aWhichTab) => {
is(aWhichTab, "current", "Should use the current tab for the search page."); is(aWhichTab, "current", "Should use the current tab for the search page.");
openUILinkInCalled = true; openUILinkInCalled = true;
if (!expectOpenUILinkInCall) { if (!expectOpenUILinkInCall) {
ok(false, "OpenUILink in was called when it shouldn't have been."); ok(false, "OpenUILinkIn was called when it shouldn't have been.");
} }
}; };
logActiveElement(); logActiveElement();
function* waitForSearchBarFocus()
{
let searchbar = document.getElementById("searchbar");
yield waitForCondition(function () {
logActiveElement();
return document.activeElement === searchbar.textbox.inputField;
});
}
// Ctrl+K should open the menu panel and focus the search bar if the search bar is in the panel. // Ctrl+K should open the menu panel and focus the search bar if the search bar is in the panel.
add_task(function() { add_task(function() {
let searchbar = document.getElementById("searchbar"); let searchbar = document.getElementById("searchbar");
@ -28,8 +37,7 @@ add_task(function() {
sendWebSearchKeyCommand(); sendWebSearchKeyCommand();
yield shownPanelPromise; yield shownPanelPromise;
logActiveElement(); yield waitForSearchBarFocus();
is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused");
let hiddenPanelPromise = promisePanelHidden(window); let hiddenPanelPromise = promisePanelHidden(window);
EventUtils.synthesizeKey("VK_ESCAPE", {}); EventUtils.synthesizeKey("VK_ESCAPE", {});
@ -49,8 +57,8 @@ add_task(function() {
yield shownPanelPromise; yield shownPanelPromise;
sendWebSearchKeyCommand(); sendWebSearchKeyCommand();
logActiveElement();
is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused"); yield waitForSearchBarFocus();
let hiddenPanelPromise = promisePanelHidden(window); let hiddenPanelPromise = promisePanelHidden(window);
EventUtils.synthesizeKey("VK_ESCAPE", {}); EventUtils.synthesizeKey("VK_ESCAPE", {});
@ -68,7 +76,6 @@ add_task(function() {
window.resizeTo(360, window.outerHeight); window.resizeTo(360, window.outerHeight);
yield waitForCondition(() => navbar.getAttribute("overflowing") == "true"); yield waitForCondition(() => navbar.getAttribute("overflowing") == "true");
ok(!navbar.querySelector("#search-container"), "Search container should be overflowing"); ok(!navbar.querySelector("#search-container"), "Search container should be overflowing");
let searchbar = document.getElementById("searchbar");
let shownPanelPromise = promiseOverflowShown(window); let shownPanelPromise = promiseOverflowShown(window);
sendWebSearchKeyCommand(); sendWebSearchKeyCommand();
@ -76,8 +83,8 @@ add_task(function() {
let chevron = document.getElementById("nav-bar-overflow-button"); let chevron = document.getElementById("nav-bar-overflow-button");
yield waitForCondition(function() chevron.open); yield waitForCondition(function() chevron.open);
logActiveElement();
is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused"); yield waitForSearchBarFocus();
let hiddenPanelPromise = promiseOverflowHidden(window); let hiddenPanelPromise = promiseOverflowHidden(window);
EventUtils.synthesizeKey("VK_ESCAPE", {}); EventUtils.synthesizeKey("VK_ESCAPE", {});
@ -90,13 +97,12 @@ add_task(function() {
// Ctrl+K should focus the search bar if it is in the navbar and not overflowing. // Ctrl+K should focus the search bar if it is in the navbar and not overflowing.
add_task(function() { add_task(function() {
let searchbar = document.getElementById("searchbar");
let placement = CustomizableUI.getPlacementOfWidget("search-container"); let placement = CustomizableUI.getPlacementOfWidget("search-container");
is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in nav-bar"); is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in nav-bar");
sendWebSearchKeyCommand(); sendWebSearchKeyCommand();
logActiveElement();
is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused"); yield waitForSearchBarFocus();
}); });
// Ctrl+K should open the search page if the search bar has been customized out. // Ctrl+K should open the search page if the search bar has been customized out.

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

@ -318,7 +318,13 @@ InspectorPanel.prototype = {
this._destroyMarkup(); this._destroyMarkup();
this.isDirty = false; this.isDirty = false;
this._getDefaultNodeForSelection().then(defaultNode => { let onNodeSelected = defaultNode => {
// Cancel this promise resolution as a new one had
// been queued up.
if (this._pendingSelection != onNodeSelected) {
return;
}
this._pendingSelection = null;
this.selection.setNodeFront(defaultNode, "navigateaway"); this.selection.setNodeFront(defaultNode, "navigateaway");
this._initMarkup(); this._initMarkup();
@ -330,7 +336,9 @@ InspectorPanel.prototype = {
this.setupSearchBox(); this.setupSearchBox();
this.emit("new-root"); this.emit("new-root");
}); });
}); };
this._pendingSelection = onNodeSelected;
this._getDefaultNodeForSelection().then(onNodeSelected);
}, },
_selectionCssSelector: null, _selectionCssSelector: null,

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

@ -19,6 +19,7 @@ skip-if = true
[browser_inspector_markup_edit_4.js] [browser_inspector_markup_edit_4.js]
[browser_inspector_markup_add_attributes.js] [browser_inspector_markup_add_attributes.js]
[browser_inspector_markup_edit_outerhtml.js] [browser_inspector_markup_edit_outerhtml.js]
skip-if = os == 'linux' && debug # bug 970240
[browser_inspector_markup_edit_outerhtml2.js] [browser_inspector_markup_edit_outerhtml2.js]
[browser_inspector_markup_mutation.js] [browser_inspector_markup_mutation.js]
[browser_inspector_markup_mutation_flashing.js] [browser_inspector_markup_mutation_flashing.js]

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

@ -766,7 +766,7 @@ CSSCompleter.prototype = {
completeProperties: function(startProp) { completeProperties: function(startProp) {
let finalList = []; let finalList = [];
if (!startProp) if (!startProp)
return finalList; return Promise.resolve(finalList);
let length = propertyNames.length; let length = propertyNames.length;
let i = 0, count = 0; let i = 0, count = 0;

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

@ -175,6 +175,7 @@ function testPropDelete(aProp)
waitForSuccess({ waitForSuccess({
name: "property deleted", name: "property deleted",
timeout: 60000,
validatorFn: () => !("testUpdatedProp" in content.wrappedJSObject.fooObj), validatorFn: () => !("testUpdatedProp" in content.wrappedJSObject.fooObj),
successFn: finishTest, successFn: finishTest,
failureFn: finishTest, failureFn: finishTest,

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

@ -4,64 +4,39 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Tests that errors still show up in the Web Console after a page reload. // Tests that errors still show up in the Web Console after a page reload.
// See bug 580030: the error handler fails silently after page reload.
// https://bugzilla.mozilla.org/show_bug.cgi?id=580030
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-error.html"; const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-error.html";
function test() { function test() {
expectUncaughtException(); Task.spawn(function*() {
addTab(TEST_URI); const {tab} = yield loadTab(TEST_URI);
browser.addEventListener("load", onLoad, true); const hud = yield openConsole(tab);
} info("console opened");
// see bug 580030: the error handler fails silently after page reload. executeSoon(() => {
// https://bugzilla.mozilla.org/show_bug.cgi?id=580030 hud.jsterm.clearOutput();
function onLoad(aEvent) { info("wait for reload");
browser.removeEventListener(aEvent.type, onLoad, true); content.location.reload();
openConsole(null, function(hud) {
hud.jsterm.clearOutput();
browser.addEventListener("load", testErrorsAfterPageReload, true);
content.location.reload();
});
}
function testErrorsAfterPageReload(aEvent) {
browser.removeEventListener(aEvent.type, testErrorsAfterPageReload, true);
// dispatch a click event to the button in the test page and listen for
// errors.
Services.console.registerListener(consoleObserver);
let button = content.document.querySelector("button").wrappedJSObject;
ok(button, "button found");
EventUtils.sendMouseEvent({type: "click"}, button, content.wrappedJSObject);
}
var consoleObserver = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
observe: function test_observe(aMessage)
{
// Ignore errors we don't care about.
if (!(aMessage instanceof Ci.nsIScriptError) ||
aMessage.category != "content javascript") {
return;
}
Services.console.unregisterListener(this);
let outputNode = HUDService.getHudByWindow(content).outputNode;
waitForSuccess({
name: "error message after page reload",
validatorFn: function()
{
return outputNode.textContent.indexOf("fooBazBaz") > -1;
},
successFn: finishTest,
failureFn: finishTest,
}); });
}
};
yield hud.target.once("navigate");
info("target navigated");
let button = content.document.querySelector("button");
ok(button, "button found");
expectUncaughtException();
EventUtils.sendMouseEvent({type: "click"}, button, content);
yield waitForMessages({
webconsole: hud,
messages: [{
text: "fooBazBaz is not defined",
category: CATEGORY_JS,
severity: SEVERITY_ERROR,
}],
});
}).then(finishTest);
}

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

@ -8,36 +8,29 @@
* *
* ***** END LICENSE BLOCK ***** */ * ***** END LICENSE BLOCK ***** */
"use strict";
const TEST_URI = "http://example.com/browser/browser/devtools/" + const TEST_URI = "http://example.com/browser/browser/devtools/" +
"webconsole/test/test-bug-597136-external-script-" + "webconsole/test/test-bug-597136-external-script-" +
"errors.html"; "errors.html";
function test() { function test() {
addTab(TEST_URI); Task.spawn(function* () {
browser.addEventListener("load", function onLoad() { const {tab} = yield loadTab(TEST_URI);
browser.removeEventListener("load", onLoad, true); const hud = yield openConsole(tab);
openConsole(null, function(hud) {
executeSoon(function() { let button = content.document.querySelector("button");
consoleOpened(hud);
}); expectUncaughtException();
EventUtils.sendMouseEvent({ type: "click" }, button, content);
yield waitForMessages({
webconsole: hud,
messages: [{
text: "bogus is not defined",
category: CATEGORY_JS,
severity: SEVERITY_ERROR,
}],
}); });
}, true); }).then(finishTest);
}
function consoleOpened(hud) {
let button = content.document.querySelector("button");
let outputNode = hud.outputNode;
expectUncaughtException();
EventUtils.sendMouseEvent({ type: "click" }, button, content);
waitForSuccess({
name: "external script error message",
validatorFn: function()
{
return outputNode.textContent.indexOf("bogus is not defined") > -1;
},
successFn: finishTest,
failureFn: finishTest,
});
} }

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

@ -52,6 +52,7 @@ const PREF_LOGGING_DUMP = PREF_LOGGING + ".dump"; // experiments.logging
const PREF_MANIFEST_URI = "manifest.uri"; // experiments.logging.manifest.uri const PREF_MANIFEST_URI = "manifest.uri"; // experiments.logging.manifest.uri
const PREF_MANIFEST_CHECKCERT = "manifest.cert.checkAttributes"; // experiments.manifest.cert.checkAttributes const PREF_MANIFEST_CHECKCERT = "manifest.cert.checkAttributes"; // experiments.manifest.cert.checkAttributes
const PREF_MANIFEST_REQUIREBUILTIN = "manifest.cert.requireBuiltin"; // experiments.manifest.cert.requireBuiltin const PREF_MANIFEST_REQUIREBUILTIN = "manifest.cert.requireBuiltin"; // experiments.manifest.cert.requireBuiltin
const PREF_FORCE_SAMPLE = "force-sample-value"; // experiments.force-sample-value
const PREF_HEALTHREPORT_ENABLED = "datareporting.healthreport.service.enabled"; const PREF_HEALTHREPORT_ENABLED = "datareporting.healthreport.service.enabled";
@ -164,6 +165,18 @@ Experiments.Policy.prototype = {
}, },
random: function () { random: function () {
let pref = gPrefs.get(PREF_FORCE_SAMPLE);
if (pref !== undefined) {
let val = Number.parseFloat(pref);
gLogger.debug("Experiments::Policy::random sample forced: " + val);
if (IsNaN(val) || val < 0) {
return 0;
}
if (val > 1) {
return 1;
}
return val;
}
return Math.random(); return Math.random();
}, },
@ -625,7 +638,7 @@ Experiments.Experiments.prototype = {
if (!entry.initFromCacheData(item)) { if (!entry.initFromCacheData(item)) {
continue; continue;
} }
experiments.set(item.id, entry); experiments.set(entry.id, entry);
} }
this._experiments = experiments; this._experiments = experiments;
@ -666,7 +679,7 @@ Experiments.Experiments.prototype = {
continue; continue;
} }
experiments.set(data.id, entry); experiments.set(entry.id, entry);
} }
// Make sure we keep experiments that are or were running. // Make sure we keep experiments that are or were running.

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

@ -170,6 +170,8 @@ pref("dom.experimental_forms", true);
pref("dom.forms.number", true); pref("dom.forms.number", true);
/* extension manager and xpinstall */ /* extension manager and xpinstall */
pref("xpinstall.whitelist.directRequest", false);
pref("xpinstall.whitelist.fileRequest", false);
pref("xpinstall.whitelist.add", "addons.mozilla.org"); pref("xpinstall.whitelist.add", "addons.mozilla.org");
pref("xpinstall.whitelist.add.180", "marketplace.firefox.com"); pref("xpinstall.whitelist.add.180", "marketplace.firefox.com");

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

@ -141,6 +141,25 @@
<action android:name="android.intent.action.SEARCH" /> <action android:name="android.intent.action.SEARCH" />
</intent-filter> </intent-filter>
<!-- For XPI installs from websites and the download manager. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:mimeType="application/x-xpinstall" />
</intent-filter>
<!-- For XPI installs from file: URLs. -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:host="" />
<data android:scheme="file" />
<data android:pathPattern=".*\\.xpi" />
</intent-filter>
#ifdef MOZ_ANDROID_BEAM #ifdef MOZ_ANDROID_BEAM
<intent-filter> <intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED"/> <action android:name="android.nfc.action.NDEF_DISCOVERED"/>

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

@ -5,12 +5,18 @@
package org.mozilla.gecko.home; package org.mozilla.gecko.home;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.db.BrowserContract.HomeItems; import org.mozilla.gecko.db.BrowserContract.HomeItems;
import org.mozilla.gecko.db.DBUtils; import org.mozilla.gecko.db.DBUtils;
import org.mozilla.gecko.home.HomeConfig.PanelConfig; import org.mozilla.gecko.home.HomeConfig.PanelConfig;
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener; import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
import org.mozilla.gecko.home.PanelLayout.DatasetHandler; import org.mozilla.gecko.home.PanelLayout.DatasetHandler;
import org.mozilla.gecko.home.PanelLayout.DatasetRequest; import org.mozilla.gecko.home.PanelLayout.DatasetRequest;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.ThreadUtils;
import android.app.Activity; import android.app.Activity;
import android.content.ContentResolver; import android.content.ContentResolver;
@ -45,7 +51,8 @@ import android.view.ViewGroup;
* See {@code PanelLayout} for more details on how {@code DynamicPanel} * See {@code PanelLayout} for more details on how {@code DynamicPanel}
* receives dataset requests and delivers them back to the {@code PanelLayout}. * receives dataset requests and delivers them back to the {@code PanelLayout}.
*/ */
public class DynamicPanel extends HomeFragment { public class DynamicPanel extends HomeFragment
implements GeckoEventListener {
private static final String LOGTAG = "GeckoDynamicPanel"; private static final String LOGTAG = "GeckoDynamicPanel";
// Dataset ID to be used by the loader // Dataset ID to be used by the loader
@ -116,12 +123,15 @@ public class DynamicPanel extends HomeFragment {
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState) { public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
GeckoAppShell.registerEventListener("HomePanels:RefreshDataset", this);
} }
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
mLayout = null; mLayout = null;
GeckoAppShell.unregisterEventListener("HomePanels:RefreshDataset", this);
} }
@Override @Override
@ -152,10 +162,69 @@ public class DynamicPanel extends HomeFragment {
mLayout.load(); mLayout.load();
} }
@Override
public void handleMessage(String event, final JSONObject message) {
if (event.equals("HomePanels:RefreshDataset")) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
handleDatasetRefreshRequest(message);
}
});
}
}
private static int generateLoaderId(String datasetId) { private static int generateLoaderId(String datasetId) {
return datasetId.hashCode(); return datasetId.hashCode();
} }
/**
* Handles a dataset refresh request from Gecko. This is usually
* triggered by a HomeStorage.save() call in an add-on.
*/
private void handleDatasetRefreshRequest(JSONObject message) {
final String datasetId;
try {
datasetId = message.getString("datasetId");
} catch (JSONException e) {
Log.e(LOGTAG, "Failed to handle dataset refresh", e);
return;
}
Log.d(LOGTAG, "Refresh request for dataset: " + datasetId);
final int loaderId = generateLoaderId(datasetId);
final LoaderManager lm = getLoaderManager();
final Loader<?> loader = (Loader<?>) lm.getLoader(loaderId);
// Only restart a loader if there's already an active one
// for the given dataset ID. Do nothing otherwise.
if (loader != null) {
final PanelDatasetLoader datasetLoader = (PanelDatasetLoader) loader;
final DatasetRequest request = datasetLoader.getRequest();
// Ensure the refresh request doesn't affect the view's filter
// stack (i.e. use DATASET_LOAD type) but keep the current
// dataset ID and filter.
final DatasetRequest newRequest =
new DatasetRequest(DatasetRequest.Type.DATASET_LOAD,
request.getDatasetId(),
request.getFilterDetail());
restartDatasetLoader(newRequest);
}
}
private void restartDatasetLoader(DatasetRequest request) {
final Bundle bundle = new Bundle();
bundle.putParcelable(DATASET_REQUEST, request);
// Ensure one loader per dataset
final int loaderId = generateLoaderId(request.getDatasetId());
getLoaderManager().restartLoader(loaderId, bundle, mLoaderCallbacks);
}
/** /**
* Used by the PanelLayout to make load and reset requests to * Used by the PanelLayout to make load and reset requests to
* the holding fragment. * the holding fragment.
@ -171,12 +240,7 @@ public class DynamicPanel extends HomeFragment {
return; return;
} }
final Bundle bundle = new Bundle(); restartDatasetLoader(request);
bundle.putParcelable(DATASET_REQUEST, request);
// Ensure one loader per dataset
final int loaderId = generateLoaderId(request.getDatasetId());
getLoaderManager().restartLoader(loaderId, bundle, mLoaderCallbacks);
} }
@Override @Override

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

@ -42,20 +42,23 @@ class PanelItemView extends LinearLayout {
// Only show title if the item has one // Only show title if the item has one
final boolean hasTitle = !TextUtils.isEmpty(title); final boolean hasTitle = !TextUtils.isEmpty(title);
mTitleDescContainer.setVisibility(hasTitle ? View.VISIBLE : View.GONE); mTitle.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
if (hasTitle) { if (hasTitle) {
mTitle.setText(title); mTitle.setText(title);
int descriptionIndex = cursor.getColumnIndexOrThrow(HomeItems.DESCRIPTION);
final String description = cursor.getString(descriptionIndex);
final boolean hasDescription = !TextUtils.isEmpty(description);
mDescription.setVisibility(hasDescription ? View.VISIBLE : View.GONE);
if (hasDescription) {
mDescription.setText(description);
}
} }
int descriptionIndex = cursor.getColumnIndexOrThrow(HomeItems.DESCRIPTION);
final String description = cursor.getString(descriptionIndex);
// Only show description if the item has one
final boolean hasDescription = !TextUtils.isEmpty(description);
mDescription.setVisibility(hasDescription ? View.VISIBLE : View.GONE);
if (hasDescription) {
mDescription.setText(description);
}
mTitleDescContainer.setVisibility(hasTitle || hasDescription ? View.VISIBLE : View.GONE);
int imageIndex = cursor.getColumnIndexOrThrow(HomeItems.IMAGE_URL); int imageIndex = cursor.getColumnIndexOrThrow(HomeItems.IMAGE_URL);
final String imageUrl = cursor.getString(imageIndex); final String imageUrl = cursor.getString(imageIndex);

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

@ -9,6 +9,7 @@
android:layout_width="54dp" android:layout_width="54dp"
android:layout_height="44dp" android:layout_height="44dp"
android:layout_marginTop="10dip" android:layout_marginTop="10dip"
android:layout_marginBottom="10dip"
android:layout_marginLeft="10dip" android:layout_marginLeft="10dip"
android:scaleType="centerCrop"/> android:scaleType="centerCrop"/>
@ -34,6 +35,7 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="2" android:layout_weight="2"
android:gravity="center_vertical"
android:maxLength="1024"/> android:maxLength="1024"/>
</LinearLayout> </LinearLayout>

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

@ -35,7 +35,7 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginTop="3dp" android:gravity="center_vertical"
android:singleLine="true" android:singleLine="true"
android:maxLength="1024"/> android:maxLength="1024"/>

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

@ -5695,7 +5695,10 @@ var XPInstallObserver = {
if (!tab) if (!tab)
return; return;
let host = installInfo.originatingURI.host; let host = null;
if (installInfo.originatingURI) {
host = installInfo.originatingURI.host;
}
let brandShortName = Strings.brand.GetStringFromName("brandShortName"); let brandShortName = Strings.brand.GetStringFromName("brandShortName");
let notificationName, buttons, message; let notificationName, buttons, message;
@ -5723,7 +5726,23 @@ var XPInstallObserver = {
} }
} else { } else {
notificationName = "xpinstall"; notificationName = "xpinstall";
message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2); if (host) {
// We have a host which asked for the install.
message = strings.formatStringFromName("xpinstallPromptWarning2", [brandShortName, host], 2);
} else {
// Without a host we address the add-on as the initiator of the install.
let addon = null;
if (installInfo.installs.length > 0) {
addon = installInfo.installs[0].name;
}
if (addon) {
// We have an addon name, show the regular message.
message = strings.formatStringFromName("xpinstallPromptWarningLocal", [brandShortName, addon], 2);
} else {
// We don't have an addon name, show an alternative message.
message = strings.formatStringFromName("xpinstallPromptWarningDirect", [brandShortName], 1);
}
}
buttons = [{ buttons = [{
label: strings.GetStringFromName("xpinstallPromptAllowButton"), label: strings.GetStringFromName("xpinstallPromptAllowButton"),

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

@ -71,6 +71,8 @@ blockPopups.label=Block Popups
# XPInstall # XPInstall
xpinstallPromptWarning2=%S prevented this site (%S) from asking you to install software on your device. xpinstallPromptWarning2=%S prevented this site (%S) from asking you to install software on your device.
xpinstallPromptWarningLocal=%S prevented this add-on (%S) from installing on your device.
xpinstallPromptWarningDirect=%S prevented an add-on from installing on your device.
xpinstallPromptAllowButton=Allow xpinstallPromptAllowButton=Allow
xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator. xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator.
xpinstallDisabledMessage2=Software installation is currently disabled. Press Enable and try again. xpinstallDisabledMessage2=Software installation is currently disabled. Press Enable and try again.

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

@ -9,6 +9,7 @@ this.EXPORTED_SYMBOLS = [ "HomeProvider" ];
const { utils: Cu, classes: Cc, interfaces: Ci } = Components; const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
Cu.import("resource://gre/modules/Messaging.jsm");
Cu.import("resource://gre/modules/osfile.jsm"); Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Promise.jsm"); Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Services.jsm");
@ -88,6 +89,10 @@ var gTimerRegistered = false;
// Map of datasetId -> { interval: <integer>, callback: <function> } // Map of datasetId -> { interval: <integer>, callback: <function> }
var gSyncCallbacks = {}; var gSyncCallbacks = {};
// Whether or not writes to the provider are expected.
// e.g. save() and deleteAll()
var gWritesAreExpected = false;
/** /**
* nsITimerCallback implementation. Checks to see if it's time to sync any registered datasets. * nsITimerCallback implementation. Checks to see if it's time to sync any registered datasets.
* *
@ -153,7 +158,10 @@ this.HomeProvider = Object.freeze({
return false; return false;
} }
gWritesAreExpected = true;
callback(datasetId); callback(datasetId);
gWritesAreExpected = false;
return true; return true;
}, },
@ -291,6 +299,10 @@ HomeStorage.prototype = {
* @resolves When the operation has completed. * @resolves When the operation has completed.
*/ */
save: function(data) { save: function(data) {
if (!gWritesAreExpected) {
Cu.reportError("HomeStorage: save() called outside of sync window");
}
return Task.spawn(function save_task() { return Task.spawn(function save_task() {
let db = yield getDatabaseConnection(); let db = yield getDatabaseConnection();
try { try {
@ -315,6 +327,11 @@ HomeStorage.prototype = {
} finally { } finally {
yield db.close(); yield db.close();
} }
sendMessageToJava({
type: "HomePanels:RefreshDataset",
datasetId: this.datasetId,
});
}.bind(this)); }.bind(this));
}, },
@ -325,6 +342,10 @@ HomeStorage.prototype = {
* @resolves When the operation has completed. * @resolves When the operation has completed.
*/ */
deleteAll: function() { deleteAll: function() {
if (!gWritesAreExpected) {
Cu.reportError("HomeStorage: deleteAll() called outside of sync window");
}
return Task.spawn(function delete_all_task() { return Task.spawn(function delete_all_task() {
let db = yield getDatabaseConnection(); let db = yield getDatabaseConnection();
try { try {
@ -333,6 +354,11 @@ HomeStorage.prototype = {
} finally { } finally {
yield db.close(); yield db.close();
} }
sendMessageToJava({
type: "HomePanels:RefreshDataset",
datasetId: this.datasetId,
});
}.bind(this)); }.bind(this));
} }
}; };

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

@ -42,7 +42,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm"); "resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise", XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js"); "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated", XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
"resource://gre/modules/Deprecated.jsm"); "resource://gre/modules/Deprecated.jsm");

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

@ -6,7 +6,7 @@ const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
Components.utils.import("resource://gre/modules/NetUtil.jsm"); Components.utils.import("resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise", XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js"); "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task", XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm"); "resource://gre/modules/Task.jsm");

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

@ -27,7 +27,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm"); "resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise", XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js"); "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services", XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm"); "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task", XPCOMUtils.defineLazyModuleGetter(this, "Task",

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

@ -112,12 +112,14 @@ add_task(function test() {
checkOrder(id1, id3, id2); checkOrder(id1, id3, id2);
// Add a visit, then check frecency ordering. // Add a visit, then check frecency ordering.
yield promiseAddVisits({ uri: uri2,
transition: TRANSITION_TYPED});
// When the bookmarks service gets onVisit, it asynchronously fetches all // When the bookmarks service gets onVisit, it asynchronously fetches all
// items for that visit, and then notifies onItemVisited. Thus we must // items for that visit, and then notifies onItemVisited. Thus we must
// explicitly wait for that. // explicitly wait for that.
yield promiseOnItemVisited(); let waitForVisited = promiseOnItemVisited();
yield promiseAddVisits({ uri: uri2,
transition: TRANSITION_TYPED});
yield waitForVisited;
do_print("Sort by frecency desc"); do_print("Sort by frecency desc");
result.sortingMode = NHQO.SORT_BY_FRECENCY_DESCENDING; result.sortingMode = NHQO.SORT_BY_FRECENCY_DESCENDING;

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

@ -111,6 +111,21 @@ let snapshotFormatters = {
})); }));
}, },
experiments: function experiments(data) {
$.append($("experiments-tbody"), data.map(function (experiment) {
return $.new("tr", [
$.new("td", experiment.name),
$.new("td", experiment.id),
$.new("td", experiment.description),
$.new("td", experiment.active),
$.new("td", experiment.endDate),
$.new("td", [
$.new("a", experiment.detailURL, null, {href : experiment.detailURL,})
]),
]);
}));
},
modifiedPreferences: function modifiedPreferences(data) { modifiedPreferences: function modifiedPreferences(data) {
$.append($("prefs-tbody"), sortedArrayFromObject(data).map( $.append($("prefs-tbody"), sortedArrayFromObject(data).map(
function ([name, value]) { function ([name, value]) {

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

@ -317,6 +317,39 @@
</tbody> </tbody>
</table> </table>
<h2 class="major-section">
&aboutSupport.experimentsTitle;
</h2>
<table>
<thead>
<tr>
<th>
&aboutSupport.experimentName;
</th>
<th>
&aboutSupport.experimentId;
</th>
<th>
&aboutSupport.experimentDescription;
</th>
<th>
&aboutSupport.experimentActive;
</th>
<th>
&aboutSupport.experimentEndDate;
</th>
<th>
&aboutSupport.experimentHomepage;
</th>
</tr>
</thead>
<tbody id="experiments-tbody">
</tbody>
</table>
<!-- - - - - - - - - - - - - - - - - - - - - -->
</div> </div>
</body> </body>

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

@ -18,7 +18,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "OS",
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"); "resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise", XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js"); "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services", XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm"); "resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task", XPCOMUtils.defineLazyModuleGetter(this, "Task",

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

@ -1,5 +1,5 @@
XPCOMUtils.defineLazyModuleGetter(this, "Promise", XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/commonjs/sdk/core/promise.js"); "resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task", XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm"); "resource://gre/modules/Task.jsm");
Components.utils.import("resource://gre/modules/Timer.jsm", this); Components.utils.import("resource://gre/modules/Timer.jsm", this);

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

@ -20,7 +20,7 @@
const {classes: Cc, interfaces: Ci, utils: Cu} = Components; const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js"); Cu.import("resource://gre/modules/Promise.jsm");
let panel, anchor, arrow; let panel, anchor, arrow;

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

@ -15,6 +15,13 @@ Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/CrashReports.jsm"); Cu.import("resource://gre/modules/CrashReports.jsm");
#endif #endif
let Experiments;
try {
Experiments = Cu.import("resource:///modules/experiments/Experiments.jsm").Experiments;
}
catch (e) {
}
// We use a preferences whitelist to make sure we only show preferences that // We use a preferences whitelist to make sure we only show preferences that
// are useful for support and won't compromise the user's privacy. Note that // are useful for support and won't compromise the user's privacy. Note that
// entries are *prefixes*: for example, "accessibility." applies to all prefs // entries are *prefixes*: for example, "accessibility." applies to all prefs
@ -174,6 +181,18 @@ let dataProviders = {
}); });
}, },
experiments: function experiments(done) {
if (Experiments === undefined) {
done([]);
return;
}
// getExperiments promises experiment history
Experiments.instance().getExperiments().then(
experiments => done(experiments)
);
},
modifiedPreferences: function modifiedPreferences(done) { modifiedPreferences: function modifiedPreferences(done) {
function getPref(name) { function getPref(name) {
let table = {}; let table = {};

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

@ -366,6 +366,9 @@ const SNAPSHOT_SCHEMA = {
}, },
}, },
}, },
experiments: {
type: "array",
},
}, },
}; };

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

@ -67,6 +67,8 @@ const PREF_EM_AUTO_DISABLED_SCOPES = "extensions.autoDisableScopes";
const PREF_EM_SHOW_MISMATCH_UI = "extensions.showMismatchUI"; const PREF_EM_SHOW_MISMATCH_UI = "extensions.showMismatchUI";
const PREF_XPI_ENABLED = "xpinstall.enabled"; const PREF_XPI_ENABLED = "xpinstall.enabled";
const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required"; const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required";
const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest";
const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest";
const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall."; const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall.";
const PREF_XPI_UNPACK = "extensions.alwaysUnpack"; const PREF_XPI_UNPACK = "extensions.alwaysUnpack";
const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts"; const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
@ -3445,6 +3447,28 @@ var XPIProvider = {
return Prefs.getBoolPref(PREF_XPI_ENABLED, true); return Prefs.getBoolPref(PREF_XPI_ENABLED, true);
}, },
/**
* Called to test whether installing XPI add-ons by direct URL requests is
* whitelisted.
*
* @return true if installing by direct requests is whitelisted
*/
isDirectRequestWhitelisted: function XPI_isDirectRequestWhitelisted() {
// Default to whitelisted if the preference does not exist.
return Prefs.getBoolPref(PREF_XPI_DIRECT_WHITELISTED, true);
},
/**
* Called to test whether installing XPI add-ons from file referrers is
* whitelisted.
*
* @return true if installing from file referrers is whitelisted
*/
isFileRequestWhitelisted: function XPI_isFileRequestWhitelisted() {
// Default to whitelisted if the preference does not exist.
return Prefs.getBoolPref(PREF_XPI_FILE_WHITELISTED, true);
},
/** /**
* Called to test whether installing XPI add-ons from a URI is allowed. * Called to test whether installing XPI add-ons from a URI is allowed.
* *
@ -3456,11 +3480,13 @@ var XPIProvider = {
if (!this.isInstallEnabled()) if (!this.isInstallEnabled())
return false; return false;
// Direct requests without a referrer are either whitelisted or blocked.
if (!aUri) if (!aUri)
return true; return this.isDirectRequestWhitelisted();
// file: and chrome: don't need whitelisted hosts // Local referrers can be whitelisted.
if (aUri.schemeIs("chrome") || aUri.schemeIs("file")) if (this.isFileRequestWhitelisted() &&
(aUri.schemeIs("chrome") || aUri.schemeIs("file")))
return true; return true;
this.importPermissions(); this.importPermissions();

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

@ -341,31 +341,36 @@ function DBAddonInternal(aLoaded) {
}); });
} }
DBAddonInternal.prototype = { function DBAddonInternalPrototype()
applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) { {
this.targetApplications.forEach(function(aTargetApp) { this.applyCompatibilityUpdate =
aUpdate.targetApplications.forEach(function(aUpdateTarget) { function(aUpdate, aSyncCompatibility) {
if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility || this.targetApplications.forEach(function(aTargetApp) {
Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) { aUpdate.targetApplications.forEach(function(aUpdateTarget) {
aTargetApp.minVersion = aUpdateTarget.minVersion; if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility ||
aTargetApp.maxVersion = aUpdateTarget.maxVersion; Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) {
XPIDatabase.saveChanges(); aTargetApp.minVersion = aUpdateTarget.minVersion;
} aTargetApp.maxVersion = aUpdateTarget.maxVersion;
XPIDatabase.saveChanges();
}
});
}); });
}); XPIProvider.updateAddonDisabledState(this);
XPIProvider.updateAddonDisabledState(this); };
},
get inDatabase() { this.toJSON =
return true; function() {
}, return copyProperties(this, PROP_JSON_FIELDS);
};
toJSON: function() { Object.defineProperty(this, "inDatabase",
return copyProperties(this, PROP_JSON_FIELDS); { get: function() { return true; },
} enumerable: true,
configurable: true });
} }
DBAddonInternalPrototype.prototype = AddonInternal.prototype;
DBAddonInternal.prototype.__proto__ = AddonInternal.prototype; DBAddonInternal.prototype = new DBAddonInternalPrototype();
/** /**
* Internal interface: find an addon from an already loaded addonDB * Internal interface: find an addon from an already loaded addonDB

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

@ -62,6 +62,8 @@ support-files =
[browser_installchrome.js] [browser_installchrome.js]
[browser_localfile.js] [browser_localfile.js]
[browser_localfile2.js] [browser_localfile2.js]
[browser_localfile3.js]
[browser_localfile4.js]
[browser_multipackage.js] [browser_multipackage.js]
[browser_navigateaway.js] [browser_navigateaway.js]
[browser_navigateaway2.js] [browser_navigateaway2.js]
@ -84,3 +86,4 @@ support-files =
[browser_whitelist4.js] [browser_whitelist4.js]
[browser_whitelist5.js] [browser_whitelist5.js]
[browser_whitelist6.js] [browser_whitelist6.js]
[browser_whitelist7.js]

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

@ -0,0 +1,37 @@
// ----------------------------------------------------------------------------
// Tests installing an add-on from a local file with whitelisting disabled.
// This should be blocked by the whitelist check.
function test() {
Harness.installBlockedCallback = allow_blocked;
Harness.installsCompletedCallback = finish_test;
Harness.setup();
// Disable direct request whitelisting, installing from file should be blocked.
Services.prefs.setBoolPref("xpinstall.whitelist.directRequest", false);
var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
.getService(Components.interfaces.nsIChromeRegistry);
var chromeroot = extractChromeRoot(gTestPath);
try {
var xpipath = cr.convertChromeURL(makeURI(chromeroot + "unsigned.xpi")).spec;
} catch (ex) {
var xpipath = chromeroot + "unsigned.xpi"; //scenario where we are running from a .jar and already extracted
}
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(xpipath);
}
function allow_blocked(installInfo) {
ok(true, "Seen blocked");
return false;
}
function finish_test(count) {
is(count, 0, "No add-ons should have been installed");
Services.prefs.clearUserPref("xpinstall.whitelist.directRequest");
gBrowser.removeCurrentTab();
Harness.finish();
}

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

@ -0,0 +1,40 @@
// ----------------------------------------------------------------------------
// Tests installing an add-on from a local file with whitelisting disabled.
// This should be blocked by the whitelist check.
function test() {
Harness.installBlockedCallback = allow_blocked;
Harness.installsCompletedCallback = finish_test;
Harness.setup();
// Disable file request whitelisting, installing by file referrer should be blocked.
Services.prefs.setBoolPref("xpinstall.whitelist.fileRequest", false);
var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
.getService(Components.interfaces.nsIChromeRegistry);
var chromeroot = extractChromeRoot(gTestPath);
try {
var xpipath = cr.convertChromeURL(makeURI(chromeroot)).spec;
} catch (ex) {
var xpipath = chromeroot; //scenario where we are running from a .jar and already extracted
}
var triggers = encodeURIComponent(JSON.stringify({
"Unsigned XPI": TESTROOT + "unsigned.xpi"
}));
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(xpipath + "installtrigger.html?" + triggers);
}
function allow_blocked(installInfo) {
ok(true, "Seen blocked");
return false;
}
function finish_test(count) {
is(count, 0, "No add-ons should have been installed");
Services.prefs.clearUserPref("xpinstall.whitelist.fileRequest");
gBrowser.removeCurrentTab();
Harness.finish();
}

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

@ -0,0 +1,30 @@
// ----------------------------------------------------------------------------
// Tests installing an unsigned add-on through a direct install request from
// web content. This should be blocked by the whitelist check because we disable
// direct request whitelisting, even though the target URI is whitelisted.
function test() {
Harness.installBlockedCallback = allow_blocked;
Harness.installsCompletedCallback = finish_test;
Harness.setup();
// Disable direct request whitelisting, installing should be blocked.
Services.prefs.setBoolPref("xpinstall.whitelist.directRequest", false);
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(TESTROOT + "unsigned.xpi");
}
function allow_blocked(installInfo) {
ok(true, "Seen blocked");
return false;
}
function finish_test(count) {
is(count, 0, "No add-ons should have been installed");
Services.perms.remove("example.org", "install");
Services.prefs.clearUserPref("xpinstall.whitelist.directRequest");
gBrowser.removeCurrentTab();
Harness.finish();
}