Merge fx-team to m-c.

This commit is contained in:
Ryan VanderMeulen 2014-03-20 16:49:14 -04:00
commit 3425ef7bb6
41 changed files with 593 additions and 176 deletions

View File

@ -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(),

View File

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

View File

@ -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/";

View File

@ -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

View File

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

View File

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

View File

@ -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?");

View File

@ -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.

View File

@ -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,

View File

@ -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]

View File

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

View File

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

View File

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

View File

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

View File

@ -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.

View File

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

View File

@ -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"/>

View File

@ -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

View File

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

View File

@ -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>

View File

@ -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"/>

View File

@ -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"),

View File

@ -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.

View File

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

View File

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

View File

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

View File

@ -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",

View File

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

View File

@ -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]) {

View File

@ -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>

View File

@ -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",

View File

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

View File

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

View File

@ -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 = {};

View File

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

View File

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

View File

@ -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

View File

@ -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]

View File

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

View File

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

View File

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