зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c.
This commit is contained in:
Коммит
a5e0e0ab4a
|
@ -1138,7 +1138,7 @@ let RemoteDebugger = {
|
|||
*/
|
||||
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 = {
|
||||
// We do not expose browser tab actors yet,
|
||||
// but we still have to define tabList.getList(),
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
const { Cc, Ci, Cu } = require("chrome");
|
||||
|
||||
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 System = require("sdk/system");
|
||||
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 URL = require("sdk/url");
|
||||
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 PROFILE_URL = ROOT_URI + "profile/";
|
||||
|
|
|
@ -177,6 +177,7 @@ SocialUI = {
|
|||
SocialShare.populateProviderMenu();
|
||||
SocialStatus.populateToolbarPalette();
|
||||
SocialMarks.populateToolbarPalette();
|
||||
SocialShare.update();
|
||||
},
|
||||
|
||||
// This handles "ActivateSocialFeature" events fired against content documents
|
||||
|
@ -514,7 +515,7 @@ SocialShare = {
|
|||
if (!provider)
|
||||
provider = SocialSidebar.provider;
|
||||
// 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)];
|
||||
provider = providers.length > 0 && providers[0];
|
||||
}
|
||||
|
@ -582,7 +583,10 @@ SocialShare = {
|
|||
// also update the relevent command's disabled state so the keyboard
|
||||
// shortcut only works when available.
|
||||
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() {
|
||||
|
@ -1411,6 +1415,7 @@ SocialMarks = {
|
|||
for (let cfg of contextMenus) {
|
||||
this._populateContextPopup(cfg, providers);
|
||||
}
|
||||
this.updatePanelButtons();
|
||||
},
|
||||
|
||||
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 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() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
let manifest = { // normal provider
|
||||
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);
|
||||
});
|
||||
runSocialTests(tests);
|
||||
}
|
||||
|
||||
let corpus = [
|
||||
|
@ -78,7 +78,7 @@ function loadURLInTab(url, callback) {
|
|||
tab.linkedBrowser.addEventListener("load", function listener() {
|
||||
is(tab.linkedBrowser.currentURI.spec, url, "tab loaded")
|
||||
tab.linkedBrowser.removeEventListener("load", listener, true);
|
||||
callback(tab);
|
||||
executeSoon(function() { callback(tab) });
|
||||
}, true);
|
||||
}
|
||||
|
||||
|
@ -101,10 +101,46 @@ function hasoptions(testOptions, options) {
|
|||
}
|
||||
|
||||
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) {
|
||||
let panel = document.getElementById("social-flyout-panel");
|
||||
SocialSidebar.show();
|
||||
let port = SocialSidebar.provider.getWorkerPort();
|
||||
let provider = Social._getProviderFromOrigin(manifest.origin);
|
||||
let port = provider.getWorkerPort();
|
||||
ok(port, "provider has a port");
|
||||
let testTab;
|
||||
let testIndex = 0;
|
||||
|
@ -120,22 +156,19 @@ var tests = {
|
|||
port.onmessage = function (e) {
|
||||
let topic = e.data.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":
|
||||
gBrowser.removeTab(testTab);
|
||||
hasoptions(testData.options, e.data.result);
|
||||
testData = corpus[testIndex++];
|
||||
if (testData) {
|
||||
runOneTest();
|
||||
executeSoon(runOneTest);
|
||||
} else {
|
||||
next();
|
||||
SocialService.removeProvider(manifest.origin, next);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
port.postMessage({topic: "test-init"});
|
||||
executeSoon(runOneTest);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,28 @@ function test() {
|
|||
}
|
||||
|
||||
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) {
|
||||
// we expect the addon install dialog to appear, we need to accept the
|
||||
// install from the dialog.
|
||||
|
@ -117,6 +139,15 @@ var tests = {
|
|||
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, 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);
|
||||
gBrowser.removeTab(tab);
|
||||
next();
|
||||
|
|
|
@ -207,6 +207,7 @@ function checkSocialUI(win) {
|
|||
let enabled = win.SocialUI.enabled;
|
||||
let active = Social.providers.length > 0 && !win.SocialUI._chromeless &&
|
||||
!PrivateBrowsingUtils.isWindowPrivate(win);
|
||||
let sidebarEnabled = win.SocialSidebar.provider ? enabled : false;
|
||||
|
||||
// if we have enabled providers, we should also have instances of those
|
||||
// providers
|
||||
|
@ -235,7 +236,7 @@ function checkSocialUI(win) {
|
|||
function isbool(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.chatbar.hidden, enabled, "chatbar visible?");
|
||||
|
||||
|
@ -276,7 +277,7 @@ function checkSocialUI(win) {
|
|||
}
|
||||
|
||||
// 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:FocusChat").hidden, enabled, "Social:FocusChat visible?");
|
||||
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.");
|
||||
openUILinkInCalled = true;
|
||||
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();
|
||||
|
||||
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.
|
||||
add_task(function() {
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
|
@ -28,8 +37,7 @@ add_task(function() {
|
|||
sendWebSearchKeyCommand();
|
||||
yield shownPanelPromise;
|
||||
|
||||
logActiveElement();
|
||||
is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused");
|
||||
yield waitForSearchBarFocus();
|
||||
|
||||
let hiddenPanelPromise = promisePanelHidden(window);
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
|
@ -49,8 +57,8 @@ add_task(function() {
|
|||
yield shownPanelPromise;
|
||||
|
||||
sendWebSearchKeyCommand();
|
||||
logActiveElement();
|
||||
is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused");
|
||||
|
||||
yield waitForSearchBarFocus();
|
||||
|
||||
let hiddenPanelPromise = promisePanelHidden(window);
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
|
@ -68,7 +76,6 @@ add_task(function() {
|
|||
window.resizeTo(360, window.outerHeight);
|
||||
yield waitForCondition(() => navbar.getAttribute("overflowing") == "true");
|
||||
ok(!navbar.querySelector("#search-container"), "Search container should be overflowing");
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
|
||||
let shownPanelPromise = promiseOverflowShown(window);
|
||||
sendWebSearchKeyCommand();
|
||||
|
@ -76,8 +83,8 @@ add_task(function() {
|
|||
|
||||
let chevron = document.getElementById("nav-bar-overflow-button");
|
||||
yield waitForCondition(function() chevron.open);
|
||||
logActiveElement();
|
||||
is(document.activeElement, searchbar.textbox.inputField, "The searchbar should be focused");
|
||||
|
||||
yield waitForSearchBarFocus();
|
||||
|
||||
let hiddenPanelPromise = promiseOverflowHidden(window);
|
||||
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.
|
||||
add_task(function() {
|
||||
let searchbar = document.getElementById("searchbar");
|
||||
let placement = CustomizableUI.getPlacementOfWidget("search-container");
|
||||
is(placement.area, CustomizableUI.AREA_NAVBAR, "Should be in nav-bar");
|
||||
|
||||
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.
|
||||
|
|
|
@ -318,7 +318,13 @@ InspectorPanel.prototype = {
|
|||
this._destroyMarkup();
|
||||
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._initMarkup();
|
||||
|
@ -330,7 +336,9 @@ InspectorPanel.prototype = {
|
|||
this.setupSearchBox();
|
||||
this.emit("new-root");
|
||||
});
|
||||
});
|
||||
};
|
||||
this._pendingSelection = onNodeSelected;
|
||||
this._getDefaultNodeForSelection().then(onNodeSelected);
|
||||
},
|
||||
|
||||
_selectionCssSelector: null,
|
||||
|
|
|
@ -19,6 +19,7 @@ skip-if = true
|
|||
[browser_inspector_markup_edit_4.js]
|
||||
[browser_inspector_markup_add_attributes.js]
|
||||
[browser_inspector_markup_edit_outerhtml.js]
|
||||
skip-if = os == 'linux' && debug # bug 970240
|
||||
[browser_inspector_markup_edit_outerhtml2.js]
|
||||
[browser_inspector_markup_mutation.js]
|
||||
[browser_inspector_markup_mutation_flashing.js]
|
||||
|
|
|
@ -766,7 +766,7 @@ CSSCompleter.prototype = {
|
|||
completeProperties: function(startProp) {
|
||||
let finalList = [];
|
||||
if (!startProp)
|
||||
return finalList;
|
||||
return Promise.resolve(finalList);
|
||||
|
||||
let length = propertyNames.length;
|
||||
let i = 0, count = 0;
|
||||
|
|
|
@ -175,6 +175,7 @@ function testPropDelete(aProp)
|
|||
|
||||
waitForSuccess({
|
||||
name: "property deleted",
|
||||
timeout: 60000,
|
||||
validatorFn: () => !("testUpdatedProp" in content.wrappedJSObject.fooObj),
|
||||
successFn: finishTest,
|
||||
failureFn: finishTest,
|
||||
|
|
|
@ -4,64 +4,39 @@
|
|||
* 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.
|
||||
// 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";
|
||||
|
||||
function test() {
|
||||
expectUncaughtException();
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", onLoad, true);
|
||||
}
|
||||
Task.spawn(function*() {
|
||||
const {tab} = yield loadTab(TEST_URI);
|
||||
const hud = yield openConsole(tab);
|
||||
info("console opened");
|
||||
|
||||
// see bug 580030: the error handler fails silently after page reload.
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=580030
|
||||
function onLoad(aEvent) {
|
||||
browser.removeEventListener(aEvent.type, onLoad, true);
|
||||
|
||||
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,
|
||||
executeSoon(() => {
|
||||
hud.jsterm.clearOutput();
|
||||
info("wait for reload");
|
||||
content.location.reload();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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 ***** */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/" +
|
||||
"webconsole/test/test-bug-597136-external-script-" +
|
||||
"errors.html";
|
||||
|
||||
function test() {
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
openConsole(null, function(hud) {
|
||||
executeSoon(function() {
|
||||
consoleOpened(hud);
|
||||
});
|
||||
Task.spawn(function* () {
|
||||
const {tab} = yield loadTab(TEST_URI);
|
||||
const hud = yield openConsole(tab);
|
||||
|
||||
let button = content.document.querySelector("button");
|
||||
|
||||
expectUncaughtException();
|
||||
EventUtils.sendMouseEvent({ type: "click" }, button, content);
|
||||
|
||||
yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: "bogus is not defined",
|
||||
category: CATEGORY_JS,
|
||||
severity: SEVERITY_ERROR,
|
||||
}],
|
||||
});
|
||||
}, true);
|
||||
}
|
||||
|
||||
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,
|
||||
});
|
||||
}).then(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_CHECKCERT = "manifest.cert.checkAttributes"; // experiments.manifest.cert.checkAttributes
|
||||
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";
|
||||
|
||||
|
@ -164,6 +165,18 @@ Experiments.Policy.prototype = {
|
|||
},
|
||||
|
||||
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();
|
||||
},
|
||||
|
||||
|
@ -625,7 +638,7 @@ Experiments.Experiments.prototype = {
|
|||
if (!entry.initFromCacheData(item)) {
|
||||
continue;
|
||||
}
|
||||
experiments.set(item.id, entry);
|
||||
experiments.set(entry.id, entry);
|
||||
}
|
||||
|
||||
this._experiments = experiments;
|
||||
|
@ -666,7 +679,7 @@ Experiments.Experiments.prototype = {
|
|||
continue;
|
||||
}
|
||||
|
||||
experiments.set(data.id, entry);
|
||||
experiments.set(entry.id, entry);
|
||||
}
|
||||
|
||||
// Make sure we keep experiments that are or were running.
|
||||
|
|
|
@ -170,6 +170,8 @@ pref("dom.experimental_forms", true);
|
|||
pref("dom.forms.number", true);
|
||||
|
||||
/* 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.180", "marketplace.firefox.com");
|
||||
|
||||
|
|
|
@ -141,6 +141,25 @@
|
|||
<action android:name="android.intent.action.SEARCH" />
|
||||
</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
|
||||
<intent-filter>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||
|
|
|
@ -5,12 +5,18 @@
|
|||
|
||||
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.DBUtils;
|
||||
import org.mozilla.gecko.home.HomeConfig.PanelConfig;
|
||||
import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
|
||||
import org.mozilla.gecko.home.PanelLayout.DatasetHandler;
|
||||
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.content.ContentResolver;
|
||||
|
@ -45,7 +51,8 @@ import android.view.ViewGroup;
|
|||
* See {@code PanelLayout} for more details on how {@code DynamicPanel}
|
||||
* 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";
|
||||
|
||||
// Dataset ID to be used by the loader
|
||||
|
@ -116,12 +123,15 @@ public class DynamicPanel extends HomeFragment {
|
|||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
GeckoAppShell.registerEventListener("HomePanels:RefreshDataset", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mLayout = null;
|
||||
|
||||
GeckoAppShell.unregisterEventListener("HomePanels:RefreshDataset", this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -152,10 +162,69 @@ public class DynamicPanel extends HomeFragment {
|
|||
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) {
|
||||
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
|
||||
* the holding fragment.
|
||||
|
@ -171,12 +240,7 @@ public class DynamicPanel extends HomeFragment {
|
|||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
restartDatasetLoader(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -42,20 +42,23 @@ class PanelItemView extends LinearLayout {
|
|||
|
||||
// Only show title if the item has one
|
||||
final boolean hasTitle = !TextUtils.isEmpty(title);
|
||||
mTitleDescContainer.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
|
||||
mTitle.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
|
||||
if (hasTitle) {
|
||||
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);
|
||||
final String imageUrl = cursor.getString(imageIndex);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
android:layout_width="54dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginTop="10dip"
|
||||
android:layout_marginBottom="10dip"
|
||||
android:layout_marginLeft="10dip"
|
||||
android:scaleType="centerCrop"/>
|
||||
|
||||
|
@ -34,6 +35,7 @@
|
|||
android:layout_width="fill_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="2"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLength="1024"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
android:layout_width="fill_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="3dp"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:maxLength="1024"/>
|
||||
|
||||
|
|
|
@ -5695,7 +5695,10 @@ var XPInstallObserver = {
|
|||
if (!tab)
|
||||
return;
|
||||
|
||||
let host = installInfo.originatingURI.host;
|
||||
let host = null;
|
||||
if (installInfo.originatingURI) {
|
||||
host = installInfo.originatingURI.host;
|
||||
}
|
||||
|
||||
let brandShortName = Strings.brand.GetStringFromName("brandShortName");
|
||||
let notificationName, buttons, message;
|
||||
|
@ -5723,7 +5726,23 @@ var XPInstallObserver = {
|
|||
}
|
||||
} else {
|
||||
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 = [{
|
||||
label: strings.GetStringFromName("xpinstallPromptAllowButton"),
|
||||
|
|
|
@ -71,6 +71,8 @@ blockPopups.label=Block Popups
|
|||
|
||||
# XPInstall
|
||||
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
|
||||
xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator.
|
||||
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;
|
||||
|
||||
Cu.import("resource://gre/modules/Messaging.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
@ -88,6 +89,10 @@ var gTimerRegistered = false;
|
|||
// Map of datasetId -> { interval: <integer>, callback: <function> }
|
||||
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.
|
||||
*
|
||||
|
@ -153,7 +158,10 @@ this.HomeProvider = Object.freeze({
|
|||
return false;
|
||||
}
|
||||
|
||||
gWritesAreExpected = true;
|
||||
callback(datasetId);
|
||||
gWritesAreExpected = false;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
|
@ -291,6 +299,10 @@ HomeStorage.prototype = {
|
|||
* @resolves When the operation has completed.
|
||||
*/
|
||||
save: function(data) {
|
||||
if (!gWritesAreExpected) {
|
||||
Cu.reportError("HomeStorage: save() called outside of sync window");
|
||||
}
|
||||
|
||||
return Task.spawn(function save_task() {
|
||||
let db = yield getDatabaseConnection();
|
||||
try {
|
||||
|
@ -315,6 +327,11 @@ HomeStorage.prototype = {
|
|||
} finally {
|
||||
yield db.close();
|
||||
}
|
||||
|
||||
sendMessageToJava({
|
||||
type: "HomePanels:RefreshDataset",
|
||||
datasetId: this.datasetId,
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
|
@ -325,6 +342,10 @@ HomeStorage.prototype = {
|
|||
* @resolves When the operation has completed.
|
||||
*/
|
||||
deleteAll: function() {
|
||||
if (!gWritesAreExpected) {
|
||||
Cu.reportError("HomeStorage: deleteAll() called outside of sync window");
|
||||
}
|
||||
|
||||
return Task.spawn(function delete_all_task() {
|
||||
let db = yield getDatabaseConnection();
|
||||
try {
|
||||
|
@ -333,6 +354,11 @@ HomeStorage.prototype = {
|
|||
} finally {
|
||||
yield db.close();
|
||||
}
|
||||
|
||||
sendMessageToJava({
|
||||
type: "HomePanels:RefreshDataset",
|
||||
datasetId: this.datasetId,
|
||||
});
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -42,7 +42,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
|||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
|
||||
"resource://gre/modules/Deprecated.jsm");
|
||||
|
|
|
@ -6,7 +6,7 @@ const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
|
|||
Components.utils.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
|
|
|
@ -112,12 +112,14 @@ add_task(function test() {
|
|||
checkOrder(id1, id3, id2);
|
||||
|
||||
// Add a visit, then check frecency ordering.
|
||||
yield promiseAddVisits({ uri: uri2,
|
||||
transition: TRANSITION_TYPED});
|
||||
|
||||
// When the bookmarks service gets onVisit, it asynchronously fetches all
|
||||
// items for that visit, and then notifies onItemVisited. Thus we must
|
||||
// explicitly wait for that.
|
||||
yield promiseOnItemVisited();
|
||||
let waitForVisited = promiseOnItemVisited();
|
||||
yield promiseAddVisits({ uri: uri2,
|
||||
transition: TRANSITION_TYPED});
|
||||
yield waitForVisited;
|
||||
|
||||
do_print("Sort by frecency desc");
|
||||
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) {
|
||||
$.append($("prefs-tbody"), sortedArrayFromObject(data).map(
|
||||
function ([name, value]) {
|
||||
|
|
|
@ -317,6 +317,39 @@
|
|||
</tbody>
|
||||
</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>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -18,7 +18,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
Components.utils.import("resource://gre/modules/Timer.jsm", this);
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
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;
|
||||
|
||||
|
|
|
@ -15,6 +15,13 @@ Cu.import("resource://gre/modules/Services.jsm");
|
|||
Cu.import("resource://gre/modules/CrashReports.jsm");
|
||||
#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
|
||||
// are useful for support and won't compromise the user's privacy. Note that
|
||||
// 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) {
|
||||
function getPref(name) {
|
||||
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_XPI_ENABLED = "xpinstall.enabled";
|
||||
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_UNPACK = "extensions.alwaysUnpack";
|
||||
const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
|
||||
|
@ -3445,6 +3447,28 @@ var XPIProvider = {
|
|||
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.
|
||||
*
|
||||
|
@ -3456,11 +3480,13 @@ var XPIProvider = {
|
|||
if (!this.isInstallEnabled())
|
||||
return false;
|
||||
|
||||
// Direct requests without a referrer are either whitelisted or blocked.
|
||||
if (!aUri)
|
||||
return true;
|
||||
return this.isDirectRequestWhitelisted();
|
||||
|
||||
// file: and chrome: don't need whitelisted hosts
|
||||
if (aUri.schemeIs("chrome") || aUri.schemeIs("file"))
|
||||
// Local referrers can be whitelisted.
|
||||
if (this.isFileRequestWhitelisted() &&
|
||||
(aUri.schemeIs("chrome") || aUri.schemeIs("file")))
|
||||
return true;
|
||||
|
||||
this.importPermissions();
|
||||
|
|
|
@ -341,31 +341,36 @@ function DBAddonInternal(aLoaded) {
|
|||
});
|
||||
}
|
||||
|
||||
DBAddonInternal.prototype = {
|
||||
applyCompatibilityUpdate: function DBA_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) {
|
||||
this.targetApplications.forEach(function(aTargetApp) {
|
||||
aUpdate.targetApplications.forEach(function(aUpdateTarget) {
|
||||
if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility ||
|
||||
Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) {
|
||||
aTargetApp.minVersion = aUpdateTarget.minVersion;
|
||||
aTargetApp.maxVersion = aUpdateTarget.maxVersion;
|
||||
XPIDatabase.saveChanges();
|
||||
}
|
||||
function DBAddonInternalPrototype()
|
||||
{
|
||||
this.applyCompatibilityUpdate =
|
||||
function(aUpdate, aSyncCompatibility) {
|
||||
this.targetApplications.forEach(function(aTargetApp) {
|
||||
aUpdate.targetApplications.forEach(function(aUpdateTarget) {
|
||||
if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility ||
|
||||
Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) {
|
||||
aTargetApp.minVersion = aUpdateTarget.minVersion;
|
||||
aTargetApp.maxVersion = aUpdateTarget.maxVersion;
|
||||
XPIDatabase.saveChanges();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
XPIProvider.updateAddonDisabledState(this);
|
||||
},
|
||||
XPIProvider.updateAddonDisabledState(this);
|
||||
};
|
||||
|
||||
get inDatabase() {
|
||||
return true;
|
||||
},
|
||||
this.toJSON =
|
||||
function() {
|
||||
return copyProperties(this, PROP_JSON_FIELDS);
|
||||
};
|
||||
|
||||
toJSON: function() {
|
||||
return copyProperties(this, PROP_JSON_FIELDS);
|
||||
}
|
||||
Object.defineProperty(this, "inDatabase",
|
||||
{ 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
|
||||
|
|
|
@ -62,6 +62,8 @@ support-files =
|
|||
[browser_installchrome.js]
|
||||
[browser_localfile.js]
|
||||
[browser_localfile2.js]
|
||||
[browser_localfile3.js]
|
||||
[browser_localfile4.js]
|
||||
[browser_multipackage.js]
|
||||
[browser_navigateaway.js]
|
||||
[browser_navigateaway2.js]
|
||||
|
@ -84,3 +86,4 @@ support-files =
|
|||
[browser_whitelist4.js]
|
||||
[browser_whitelist5.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();
|
||||
}
|
Загрузка…
Ссылка в новой задаче