Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2015-09-02 14:34:50 +02:00
commit 7516ef61a7
57 changed files with 1081 additions and 376 deletions

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b75979ec8862bd5799a7c42e938d3f67be38d6ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e2fab8f6ac345ecde10a1350e699be9ceb6987d6"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b75979ec8862bd5799a7c42e938d3f67be38d6ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e2fab8f6ac345ecde10a1350e699be9ceb6987d6"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b75979ec8862bd5799a7c42e938d3f67be38d6ae"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e2fab8f6ac345ecde10a1350e699be9ceb6987d6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b75979ec8862bd5799a7c42e938d3f67be38d6ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e2fab8f6ac345ecde10a1350e699be9ceb6987d6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b75979ec8862bd5799a7c42e938d3f67be38d6ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e2fab8f6ac345ecde10a1350e699be9ceb6987d6"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="05a36844c1046a1eb07d5b1325f85ed741f961ea">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b75979ec8862bd5799a7c42e938d3f67be38d6ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e2fab8f6ac345ecde10a1350e699be9ceb6987d6"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="b75979ec8862bd5799a7c42e938d3f67be38d6ae"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="e2fab8f6ac345ecde10a1350e699be9ceb6987d6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="2d58f4b9206b50b8fda0d5036da6f0c62608db7c"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="e935894ef5f27e2f04b9e929a45a958e6288a223">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b75979ec8862bd5799a7c42e938d3f67be38d6ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e2fab8f6ac345ecde10a1350e699be9ceb6987d6"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -1,9 +1,9 @@
{
"git": {
"git_revision": "b75979ec8862bd5799a7c42e938d3f67be38d6ae",
"git_revision": "e2fab8f6ac345ecde10a1350e699be9ceb6987d6",
"remote": "https://git.mozilla.org/releases/gaia.git",
"branch": ""
},
"revision": "f919c5b5f0f0b6fd3ef3346850710edafa2a615b",
"revision": "1ab9e5d9915dc22fed71384a53dc565bce1709aa",
"repo_path": "integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b75979ec8862bd5799a7c42e938d3f67be38d6ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e2fab8f6ac345ecde10a1350e699be9ceb6987d6"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="51ebaf824cc634665c5efcae95b8301ad1758c5e"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="b4f6fd4afd03161f53c7d2a663750f94762bd238"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="05a36844c1046a1eb07d5b1325f85ed741f961ea">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="b75979ec8862bd5799a7c42e938d3f67be38d6ae"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="e2fab8f6ac345ecde10a1350e699be9ceb6987d6"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9e2923fd6cab93cf88b4b9ada82225e44fe6635"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>

View File

@ -419,9 +419,14 @@
oncommand="gContextMenu.switchPageDirection();"/>
<menuseparator id="fill-login-separator" hidden="true"/>
<menu id="fill-login"
label="&fillPasswordMenu.label;"
class="menu-iconic"
accesskey="&fillPasswordMenu.accesskey;"
label="&fillLoginMenu.label;"
label-login="&fillLoginMenu.label;"
label-password="&fillPasswordMenu.label;"
label-username="&fillUsernameMenu.label;"
accesskey="&fillLoginMenu.accesskey;"
accesskey-login="&fillLoginMenu.accesskey;"
accesskey-password="&fillPasswordMenu.accesskey;"
accesskey-username="&fillUsernameMenu.accesskey;"
hidden="true">
<menupopup id="fill-login-popup">
<menuitem id="fill-login-no-logins"

View File

@ -107,6 +107,7 @@ let handleContentContextMenu = function (event) {
let frameOuterWindowID = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
let loginFillInfo = LoginManagerContent.getFieldContext(event.target);
// get referrer attribute from clicked link and parse it
// if per element referrer is enabled, the element referrer overrules
@ -168,7 +169,8 @@ let handleContentContextMenu = function (event) {
{ editFlags, spellInfo, customMenuItems, addonInfo,
principal, docLocation, charSet, baseURI, referrer,
referrerPolicy, contentType, contentDisposition,
frameOuterWindowID, selectionInfo, disableSetDesktopBg },
frameOuterWindowID, selectionInfo, disableSetDesktopBg,
loginFillInfo, },
{ event, popupNode: event.target });
}
else {
@ -190,6 +192,7 @@ let handleContentContextMenu = function (event) {
contentDisposition: contentDisposition,
selectionInfo: selectionInfo,
disableSetDesktopBackground: disableSetDesktopBg,
loginFillInfo,
};
}
}

View File

@ -506,13 +506,35 @@ nsContextMenu.prototype = {
},
initPasswordManagerItems: function() {
let showFillPassword = this.onPassword;
let disableFillPassword = !Services.logins.isLoggedIn || this.target.disabled || this.target.readOnly;
this.showItem("fill-login-separator", showFillPassword);
this.showItem("fill-login", showFillPassword);
this.setItemAttr("fill-login", "disabled", disableFillPassword);
let loginFillInfo = gContextMenuContentData && gContextMenuContentData.loginFillInfo;
if (!showFillPassword || disableFillPassword) {
// If we could not find a password field we
// don't want to show the form fill option.
let showFill = loginFillInfo && loginFillInfo.passwordField.found;
// Disable the fill option if the user has set a master password
// or if the password field or target field are disabled.
let disableFill = !loginFillInfo ||
!Services.logins ||
!Services.logins.isLoggedIn ||
loginFillInfo.passwordField.disabled ||
(!this.onPassword && loginFillInfo.usernameField.disabled);
this.showItem("fill-login-separator", showFill);
this.showItem("fill-login", showFill);
this.setItemAttr("fill-login", "disabled", disableFill);
// Set the correct label for the fill menu
let fillMenu = document.getElementById("fill-login");
if (this.onPassword) {
fillMenu.setAttribute("label", fillMenu.getAttribute("label-password"));
fillMenu.setAttribute("accesskey", fillMenu.getAttribute("accesskey-password"));
} else {
fillMenu.setAttribute("label", fillMenu.getAttribute("label-login"));
fillMenu.setAttribute("accesskey", fillMenu.getAttribute("accesskey-login"));
}
if (!showFill || disableFill) {
return;
}
let documentURI = gContextMenuContentData.documentURIObject;

View File

@ -4056,6 +4056,7 @@
frameOuterWindowID: aMessage.data.frameOuterWindowID,
selectionInfo: aMessage.data.selectionInfo,
disableSetDesktopBackground: aMessage.data.disableSetDesktopBg,
loginFillInfo: aMessage.data.loginFillInfo,
};
let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
let event = gContextMenuContentData.event;

View File

@ -80,7 +80,7 @@ function runTest(testNum) {
"context-inspect", true];
}
var passwordFillItems = [
var loginFillItems = [
"---", null,
"fill-login", null,
[
@ -652,7 +652,8 @@ function runTest(testNum) {
"context-searchselect",true,
"---", null,
"spell-check-enabled", true
].concat(inspectItems));
].concat(loginFillItems)
.concat(inspectItems));
closeContextMenu();
selectInputText(select_inputtext_password); // Select text prior to opening context menu.
openContextMenuFor(select_inputtext_password); // Invoke context menu for next test.
@ -675,7 +676,7 @@ function runTest(testNum) {
["spell-check-dictionary-en-US", true,
"---", null,
"spell-add-dictionaries", true], null
].concat(passwordFillItems)
].concat(loginFillItems)
.concat(inspectItems));
closeContextMenu();
subwindow.getSelection().removeAllRanges();

View File

@ -193,8 +193,9 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
case KeyEvent.DOM_VK_LEFT:
case KeyEvent.DOM_VK_RIGHT:
case KeyEvent.DOM_VK_HOME:
this.popup.hidePopup();
return;
// Reset the selected index so that nsAutoCompleteController
// simply closes the popup without trying to fill anything.
this.popup.selectedIndex = -1;
break;
}

View File

@ -14,6 +14,7 @@ const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start";
const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X = 7; // px
const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y = -3; // px
const BREAKPOINT_SMALL_WINDOW_WIDTH = 850; // px
const RESULTS_PANEL_POPUP_POSITION = "before_end";
const RESULTS_PANEL_MAX_RESULTS = 10;
const FILE_SEARCH_ACTION_MAX_DELAY = 300; // ms
@ -30,6 +31,7 @@ const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_
const EDITOR_VARIABLE_HOVER_DELAY = 750; // ms
const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft";
const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
const RESIZE_REFRESH_RATE = 50; // ms
const PROMISE_DEBUGGER_URL =
"chrome://browser/content/devtools/promisedebugger/promise-debugger.xhtml";
@ -93,6 +95,8 @@ let DebuggerView = {
return this._shutdown;
}
window.removeEventListener("resize", this._onResize, false);
let deferred = promise.defer();
this._shutdown = deferred.promise;
@ -141,10 +145,10 @@ let DebuggerView = {
this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth);
this.toggleInstrumentsPane({ visible: Prefs.panesVisibleOnStartup });
// Side hosts requires a different arrangement of the debugger widgets.
if (gHostType == "side") {
this.handleHostChanged(gHostType);
}
this.updateLayoutMode();
this._onResize = this._onResize.bind(this);
window.addEventListener("resize", this._onResize, false);
},
/**
@ -214,6 +218,17 @@ let DebuggerView = {
_initializePromiseDebugger: function() {
let iframe = this._promiseDebuggerIframe = document.createElement("iframe");
iframe.setAttribute("flex", 1);
let onLoad = (event) => {
iframe.removeEventListener("load", onLoad, true);
let doc = event.target;
let win = doc.defaultView;
win.setPanel(DebuggerController._toolbox);
};
iframe.addEventListener("load", onLoad, true);
iframe.setAttribute("src", PROMISE_DEBUGGER_URL);
this._promisePane.appendChild(iframe);
},
@ -223,6 +238,8 @@ let DebuggerView = {
*/
_destroyPromiseDebugger: function() {
if (this._promiseDebuggerIframe) {
this._promiseDebuggerIframe.contentWindow.destroy();
this._promiseDebuggerIframe.parentNode.removeChild(
this._promiseDebuggerIframe);
@ -619,24 +636,64 @@ let DebuggerView = {
* @param string aType
* The host type, either "bottom", "side" or "window".
*/
handleHostChanged: function(aType) {
let newLayout = "";
if (aType == "side") {
newLayout = "vertical";
this._enterVerticalLayout();
} else {
newLayout = "horizontal";
this._enterHorizontalLayout();
}
this._hostType = aType;
this._body.setAttribute("layout", newLayout);
window.emit(EVENTS.LAYOUT_CHANGED, newLayout);
handleHostChanged: function(hostType) {
this._hostType = hostType;
this.updateLayoutMode();
},
/**
* Switches the debugger widgets to a horizontal layout.
* Resize handler for this container's window.
*/
_onResize: function (evt) {
// Allow requests to settle down first.
setNamedTimeout(
"resize-events", RESIZE_REFRESH_RATE, () => this.updateLayoutMode());
},
/**
* Set the layout to "vertical" or "horizontal" depending on the host type.
*/
updateLayoutMode: function() {
if (this._isSmallWindowHost() || this._hostType == "side") {
this._setLayoutMode("vertical");
} else {
this._setLayoutMode("horizontal");
}
},
/**
* Check if the current host is in window mode and is
* too small for horizontal layout
*/
_isSmallWindowHost: function() {
if (this._hostType != "window") {
return false;
}
return window.outerWidth <= BREAKPOINT_SMALL_WINDOW_WIDTH;
},
/**
* Enter the provided layoutMode. Do nothing if the layout is the same as the current one.
* @param {String} layoutMode new layout ("vertical" or "horizontal")
*/
_setLayoutMode: function(layoutMode) {
if (this._body.getAttribute("layout") == layoutMode) {
return;
}
if (layoutMode == "vertical") {
this._enterVerticalLayout();
} else {
this._enterHorizontalLayout();
}
this._body.setAttribute("layout", layoutMode);
window.emit(EVENTS.LAYOUT_CHANGED, layoutMode);
},
/**
* Switches the debugger widgets to a vertical layout.
*/
_enterVerticalLayout: function() {
let vertContainer = document.getElementById("vertical-layout-panes-container");
@ -653,7 +710,7 @@ let DebuggerView = {
},
/**
* Switches the debugger widgets to a vertical layout.
* Switches the debugger widgets to a horizontal layout.
*/
_enterHorizontalLayout: function() {
let normContainer = document.getElementById("debugger-widgets");

View File

@ -26,7 +26,7 @@ function DebuggerPanel(iframeWindow, toolbox) {
this.unhighlightWhenResumed = this.unhighlightWhenResumed.bind(this);
EventEmitter.decorate(this);
};
}
exports.DebuggerPanel = DebuggerPanel;

View File

@ -6,15 +6,22 @@
* host changes.
*/
"use strict";
let gDefaultHostType = Services.prefs.getCharPref("devtools.toolbox.host");
function test() {
// test is too slow on some platforms due to the number of test cases
requestLongerTimeout(2);
Task.spawn(function*() {
yield testHosts(["bottom", "side", "window"], ["horizontal", "vertical", "horizontal"]);
yield testHosts(["bottom", "side", "window:big"], ["horizontal", "vertical", "horizontal"]);
yield testHosts(["side", "bottom", "side"], ["vertical", "horizontal", "vertical"]);
yield testHosts(["bottom", "side", "bottom"], ["horizontal", "vertical", "horizontal"]);
yield testHosts(["side", "window", "side"], ["vertical", "horizontal", "vertical"]);
yield testHosts(["window", "side", "window"], ["horizontal", "vertical", "horizontal"]);
yield testHosts(["side", "window:big", "side"], ["vertical", "horizontal", "vertical"]);
yield testHosts(["window:big", "side", "window:big"], ["horizontal", "vertical", "horizontal"]);
yield testHosts(["window:small", "bottom", "side"], ["vertical", "horizontal", "vertical"]);
yield testHosts(["window:small", "window:big", "window:small"], ["vertical", "horizontal", "vertical"]);
finish();
});
}
@ -23,11 +30,15 @@ function testHosts(aHostTypes, aLayoutTypes) {
let [firstHost, secondHost, thirdHost] = aHostTypes;
let [firstLayout, secondLayout, thirdLayout] = aLayoutTypes;
Services.prefs.setCharPref("devtools.toolbox.host", firstHost);
Services.prefs.setCharPref("devtools.toolbox.host", getHost(firstHost));
return Task.spawn(function*() {
let [tab, debuggee, panel] = yield initDebugger("about:blank");
yield testHost(tab, panel, firstHost, firstLayout);
if (getHost(firstHost) === "window") {
yield resizeToolboxWindow(panel, firstHost);
}
yield testHost(panel, getHost(firstHost), firstLayout);
yield switchAndTestHost(tab, panel, secondHost, secondLayout);
yield switchAndTestHost(tab, panel, thirdHost, thirdLayout);
yield teardown(panel);
@ -39,26 +50,63 @@ function switchAndTestHost(aTab, aPanel, aHostType, aLayoutType) {
let gDebugger = aPanel.panelWin;
return Task.spawn(function*() {
let layoutChanged = once(gDebugger, gDebugger.EVENTS.LAYOUT_CHANGED);
let hostChanged = gToolbox.switchHost(aHostType);
let layoutChanged = waitEventOnce(gDebugger, gDebugger.EVENTS.LAYOUT_CHANGED);
let hostChanged = gToolbox.switchHost(getHost(aHostType));
yield hostChanged;
ok(true, "The toolbox's host has changed.");
info("The toolbox's host has changed.");
if (getHost(aHostType) === "window") {
yield resizeToolboxWindow(aPanel, aHostType);
}
yield layoutChanged;
ok(true, "The debugger's layout has changed.");
info("The debugger's layout has changed.");
yield testHost(aTab, aPanel, aHostType, aLayoutType);
yield testHost(aPanel, getHost(aHostType), aLayoutType);
});
}
function once(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, deferred.resolve);
return deferred.promise;
function waitEventOnce(aTarget, aEvent) {
let deferred = promise.defer();
aTarget.once(aEvent, deferred.resolve);
return deferred.promise;
}
function getHost(host) {
if (host.indexOf("window") == 0) {
return "window";
}
return host;
}
function resizeToolboxWindow(panel, host) {
let sizeOption = host.split(":")[1];
let win = panel._toolbox._host._window;
// should be the same value as BREAKPOINT_SMALL_WINDOW_WIDTH in debugger-view.js
let breakpoint = 850;
if (sizeOption == "big" && win.outerWidth <= breakpoint) {
yield resizeAndWaitForLayoutChange(panel, breakpoint + 300);
} else if (sizeOption == "small" && win.outerWidth >= breakpoint) {
yield resizeAndWaitForLayoutChange(panel, breakpoint - 300);
} else {
info("Window resize unnecessary for host " + host);
}
}
function testHost(aTab, aPanel, aHostType, aLayoutType) {
function resizeAndWaitForLayoutChange(panel, width) {
info("Updating toolbox window width to " + width);
let win = panel._toolbox._host._window;
let gDebugger = panel.panelWin;
win.resizeTo(width, window.screen.availHeight);
yield waitEventOnce(gDebugger, gDebugger.EVENTS.LAYOUT_CHANGED);
}
function testHost(aPanel, aHostType, aLayoutType) {
let gDebugger = aPanel.panelWin;
let gView = gDebugger.DebuggerView;

View File

@ -621,7 +621,9 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
let promisePane = this.DebuggerView._promisePane;
promisePane.hidden = !promisePane.hidden;
this.DebuggerView._initializePromiseDebugger();
if (!this.DebuggerView._promiseDebuggerIframe) {
this.DebuggerView._initializePromiseDebugger();
}
}
},

View File

@ -110,7 +110,8 @@ browser.jar:
content/browser/devtools/performance/views/details-memory-flamegraph.js (performance/views/details-memory-flamegraph.js)
content/browser/devtools/performance/views/optimizations-list.js (performance/views/optimizations-list.js)
content/browser/devtools/performance/views/recordings.js (performance/views/recordings.js)
content/browser/devtools/promisedebugger/promise-debugger.js (promisedebugger/promise-debugger.js)
content/browser/devtools/promisedebugger/promise-controller.js (promisedebugger/promise-controller.js)
content/browser/devtools/promisedebugger/promise-panel.js (promisedebugger/promise-panel.js)
content/browser/devtools/promisedebugger/promise-debugger.xhtml (promisedebugger/promise-debugger.xhtml)
content/browser/devtools/commandline.css (commandline/commandline.css)
content/browser/devtools/commandlineoutput.xhtml (commandline/commandlineoutput.xhtml)

View File

@ -259,17 +259,34 @@ MarkupView.prototype = {
},
_onMouseUp: function() {
if (this._lastDropTarget) {
this.indicateDropTarget(null);
}
if (this._lastDragTarget) {
this.indicateDragTarget(null);
}
this.indicateDropTarget(null);
this.indicateDragTarget(null);
if (this._scrollInterval) {
clearInterval(this._scrollInterval);
}
},
cancelDragging: function() {
if (!this.isDragging) {
return;
}
for (let [, container] of this._containers) {
if (container.isDragging) {
container.cancelDragging();
break;
}
}
this.indicateDropTarget(null);
this.indicateDragTarget(null);
if (this._scrollInterval) {
clearInterval(this._scrollInterval);
}
},
_hoveredNode: null,
/**
@ -632,6 +649,12 @@ MarkupView.prototype = {
this.beginEditingOuterHTML(this._selectedContainer.node);
break;
}
case Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE: {
if (this.isDragging) {
this.cancelDragging();
break;
}
}
default:
handled = false;
}
@ -1927,6 +1950,18 @@ MarkupContainer.prototype = {
return this._isDragging;
},
/**
* Check if element is draggable
*/
isDraggable: function(target) {
return this._isMouseDown &&
this.markup._dragStartEl === target &&
!this.node.isPseudoElement &&
!this.node.isAnonymous &&
this.win.getSelection().isCollapsed &&
this.node.parentNode().tagName !== null;
},
_onMouseDown: function(event) {
let target = event.target;
@ -1964,9 +1999,7 @@ MarkupContainer.prototype = {
this.markup._dragStartEl = target;
setTimeout(() => {
// Make sure the mouse is still down and on target.
if (!this._isMouseDown || this.markup._dragStartEl !== target ||
this.node.isPseudoElement || this.node.isAnonymous ||
!this.win.getSelection().isCollapsed) {
if (!this.isDraggable(target)) {
return;
}
this.isDragging = true;
@ -1990,8 +2023,7 @@ MarkupContainer.prototype = {
return;
}
this.isDragging = false;
this.elt.style.removeProperty("top");
this.cancelDragging();
let dropTargetNodes = this.markup.dropTargetNodes;
@ -2021,6 +2053,16 @@ MarkupContainer.prototype = {
this.markup.indicateDropTarget(el);
},
cancelDragging: function() {
if (!this.isDragging) {
return;
}
this._isMouseDown = false;
this.isDragging = false;
this.elt.style.removeProperty("top");
},
/**
* Temporarily flash the container to attract attention.
* Used for markup mutations.

View File

@ -45,6 +45,8 @@ skip-if = e10s # scratchpad.xul is not loading in e10s window
[browser_markupview_copy_image_data.js]
[browser_markupview_css_completion_style_attribute.js]
[browser_markupview_dragdrop_autoscroll.js]
[browser_markupview_dragdrop_dragRootNode.js]
[browser_markupview_dragdrop_escapeKeyPress.js]
[browser_markupview_dragdrop_invalidNodes.js]
[browser_markupview_dragdrop_isDragging.js]
[browser_markupview_dragdrop_reorder.js]

View File

@ -0,0 +1,30 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test if html root node is draggable
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
const GRAB_DELAY = 400;
add_task(function*() {
let {inspector} = yield addTab(TEST_URL).then(openInspector);
let el = yield getContainerForSelector("html", inspector);
let rect = el.tagLine.getBoundingClientRect();
info("Simulating mouseDown on html root node");
el._onMouseDown({
target: el.tagLine,
pageX: rect.x,
pageY: rect.y,
stopPropagation: function() {},
preventDefault: function() {}
});
info("Waiting for a little bit more than the markup-view grab delay");
yield wait(GRAB_DELAY + 1);
is(el.isDragging, false, "isDragging is false");
});

View File

@ -0,0 +1,34 @@
/* vim: set ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test whether ESCAPE keypress cancels dragging of an element
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
const GRAB_DELAY = 400;
add_task(function*() {
let {inspector} = yield addTab(TEST_URL).then(openInspector);
let el = yield getContainerForSelector("#test", inspector);
let rect = el.tagLine.getBoundingClientRect();
info("Simulating mouseDown on #test");
el._onMouseDown({
target: el.tagLine,
pageX: rect.x,
pageY: rect.y,
stopPropagation: function() {},
preventDefault: function() {}
});
info("Waiting for a little bit more than the markup-view grab delay");
yield wait(GRAB_DELAY + 1);
ok(el.isDragging, "isDragging true after mouseDown");
info("Simulating ESCAPE keypress");
EventUtils.sendKey("escape", inspector.panelWin);
is(el.isDragging, false, "isDragging false after ESCAPE keypress");
});

View File

@ -7,5 +7,4 @@
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
EXTRA_JS_MODULES.devtools.promisedebugger += [
'promise-debugger.js'
]

View File

@ -0,0 +1,102 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global promise, PromisesPanel, PromisesFront, DevToolsUtils */
"use strict";
const { utils: Cu } = Components;
const { loader, require } =
Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const { Task } = require("resource://gre/modules/Task.jsm");
loader.lazyRequireGetter(this, "promise");
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
loader.lazyRequireGetter(this, "DevToolsUtils",
"devtools/toolkit/DevToolsUtils");
loader.lazyRequireGetter(this, "PromisesFront",
"devtools/server/actors/promises", true);
// Global toolbox, set when startup is called.
let gToolbox;
/**
* Initialize the promise debugger controller and view upon loading the iframe.
*/
let startup = Task.async(function*(toolbox) {
gToolbox = toolbox;
yield PromisesController.initialize(toolbox);
yield PromisesPanel.initialize();
});
/**
* Destroy the promise debugger controller and view when unloading the iframe.
*/
let shutdown = Task.async(function*() {
yield PromisesController.destroy();
yield PromisesPanel.destroy();
gToolbox = null;
});
function setPanel(toolbox) {
return startup(toolbox).catch(e =>
DevToolsUtils.reportException("setPanel", e));
}
function destroy() {
return shutdown().catch(e => DevToolsUtils.reportException("destroy", e));
}
/**
* The promisedebugger controller's job is to retrieve PromisesFronts from the
* server.
*/
let PromisesController = {
initialize: Task.async(function*() {
if (this.initialized) {
return this.initialized.promise;
}
this.initialized = promise.defer();
let target = gToolbox.target;
this.promisesFront = new PromisesFront(target.client, target.form);
yield this.promisesFront.attach();
if (this.destroyed) {
console.warn("Could not fully initialize the PromisesController");
return null;
}
this.initialized.resolve();
}),
destroy: Task.async(function*() {
if (!this.initialized) {
return null;
}
if (this.destroyed) {
return this.destroyed.promise;
}
this.destroyed = promise.defer();
if (this.promisesFront) {
yield this.promisesFront.detach();
this.promisesFront.destroy();
this.promisesFront = null;
}
this.destroyed.resolve();
}),
};
EventEmitter.decorate(PromisesController);

View File

@ -1,7 +0,0 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

View File

@ -17,6 +17,7 @@
<script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/theme-switching.js"/>
</head>
<body class="devtools-monospace" role="application">
<script type="application/javascript;version=1.8" src="promise-debugger.js"></script>
<script type="application/javascript;version=1.8" src="promise-controller.js"></script>
<script type="application/javascript;version=1.8" src="promise-panel.js"></script>
</body>
</html>

View File

@ -0,0 +1,44 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global PromisesController, promise */
"use strict";
/**
* The main promise debugger UI.
*/
let PromisesPanel = {
PANEL_INITIALIZED: "panel-initialized",
initialize: Task.async(function*() {
if (PromisesController.destroyed) {
return null;
}
if (this.initialized) {
return this.initialized.promise;
}
this.initialized = promise.defer();
this.initialized.resolve();
this.emit(this.PANEL_INITIALIZED);
}),
destroy: Task.async(function*() {
if (!this.initialized) {
return null;
}
if (this.destroyed) {
return this.destroyed.promise;
}
this.destroyed = promise.defer();
this.destroyed.resolve();
}),
};
EventEmitter.decorate(PromisesPanel);

View File

@ -95,13 +95,3 @@
#context-media-eme-learnmore {
list-style-image: url("chrome://browser/skin/drm-icon.svg#chains");
}
#fill-login {
list-style-image: url("chrome://mozapps/skin/passwordmgr/key-16.png");
}
@media (min-resolution: 1.1dppx) {
#fill-login {
list-style-image: url("chrome://mozapps/skin/passwordmgr/key-16@2x.png");
}
}

View File

@ -64,6 +64,7 @@ const COMMAND_MAP = {
'cut': 'cmd_cut',
'copy': 'cmd_copyAndCollapseToEnd',
'copyImage': 'cmd_copyImage',
'copyLink': 'cmd_copyLink',
'paste': 'cmd_paste',
'selectall': 'cmd_selectAll'
};
@ -865,7 +866,13 @@ BrowserElementChild.prototype = {
var elem = e.target;
var menuData = {systemTargets: [], contextmenu: null};
var ctxMenuId = null;
var hasImgElement = false;
var copyableElements = {
image: false,
link: false,
hasElements: function() {
return this.image || this.link;
}
};
// Set the event target as the copy image command needs it to
// determine what was context-clicked on.
@ -884,20 +891,22 @@ BrowserElementChild.prototype = {
ctxMenuId = elem.getAttribute('contextmenu');
}
// Enable copy image option
// Enable copy image/link option
if (elem.nodeName == 'IMG') {
hasImgElement = true;
copyableElements.image = true;
} else if (elem.nodeName == 'A') {
copyableElements.link = true;
}
elem = elem.parentNode;
}
if (ctxMenuId || hasImgElement) {
if (ctxMenuId || copyableElements.hasElements()) {
var menu = null;
if (ctxMenuId) {
menu = e.target.ownerDocument.getElementById(ctxMenuId);
}
menuData.contextmenu = this._buildMenuObj(menu, '', hasImgElement);
menuData.contextmenu = this._buildMenuObj(menu, '', copyableElements);
}
// Pass along the position where the context menu should be located
@ -1235,6 +1244,10 @@ BrowserElementChild.prototype = {
// Set command
data.json.command = 'copyImage';
this._recvDoCommand(data);
} else if (data.json.menuitem == 'copy-link') {
// Set command
data.json.command = 'copyLink';
this._recvDoCommand(data);
} else if (data.json.menuitem in this._ctxHandlers) {
this._ctxHandlers[data.json.menuitem].click();
this._ctxHandlers = {};
@ -1244,7 +1257,7 @@ BrowserElementChild.prototype = {
}
},
_buildMenuObj: function(menu, idPrefix, hasImgElement) {
_buildMenuObj: function(menu, idPrefix, copyableElements) {
var menuObj = {type: 'menu', items: []};
// Customized context menu
if (menu) {
@ -1263,8 +1276,14 @@ BrowserElementChild.prototype = {
}
}
}
// Note: Display "Copy Link" first in order to make sure "Copy Image" is
// put together with other image options if elem is an image link.
// "Copy Link" menu item
if (copyableElements.link) {
menuObj.items.push({id: 'copy-link'});
}
// "Copy Image" menu item
if (hasImgElement) {
if (copyableElements.image) {
menuObj.items.push({id: 'copy-image'});
}

View File

@ -24,7 +24,7 @@ function checkEmptyContextMenu() {
function checkInnerContextMenu() {
sendContextMenuTo('#inner-link', function onContextMenu(detail) {
is(detail.systemTargets.length, 1, 'Includes anchor data');
is(detail.contextmenu.items.length, 2, 'Inner clicks trigger correct menu');
is(detail.contextmenu.items.length, 3, 'Inner clicks trigger correct menu');
var target = detail.systemTargets[0];
is(target.nodeName, 'A', 'Reports correct nodeName');
is(target.data.uri, 'foo.html', 'Reports correct uri');

View File

@ -25775,7 +25775,7 @@ dialectic/SM
dialectical
dialectics/M
dialing/S
dialog/DG
dialog/DGS
dialogue/DRSMG
dialyses
dialysis/M

View File

@ -475,6 +475,7 @@ public class BrowserContract {
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/searchhistory";
public static final String QUERY = "query";
public static final String DATE = "date";
public static final String TABLE_NAME = "searchhistory";
public static final Uri CONTENT_URI = Uri.withAppendedPath(SEARCH_HISTORY_AUTHORITY_URI, "searchhistory");

View File

@ -911,7 +911,7 @@ public class BrowserSearch extends HomeFragment
final SearchEngine engine = mSearchEngines.get(position);
final boolean animate = (mAnimateSuggestions && engine.hasSuggestions());
row.updateFromSearchEngine(engine, animate);
row.updateSuggestions(mSuggestionsEnabled, engine, mSearchTerm, animate);
if (animate) {
// Only animate suggestions the first time they are shown
mAnimateSuggestions = false;

View File

@ -5,6 +5,7 @@
package org.mozilla.gecko.home;
import org.mozilla.gecko.db.BrowserContract.SearchHistory;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
@ -16,6 +17,8 @@ import org.mozilla.gecko.widget.AnimatedHeightLayout;
import org.mozilla.gecko.widget.FaviconView;
import org.mozilla.gecko.widget.FlowLayout;
import android.database.Cursor;
import android.content.ContentResolver;
import android.content.Context;
import android.util.AttributeSet;
import android.view.KeyEvent;
@ -133,7 +136,10 @@ class SearchEngineRow extends AnimatedHeightLayout {
return suggestionText.getText().toString();
}
private void setSuggestionOnView(View v, String suggestion) {
private void setSuggestionOnView(View v, String suggestion, boolean isUserSavedSearch) {
final ImageView historyIcon = (ImageView) v.findViewById(R.id.suggestion_item_icon);
historyIcon.setVisibility(isUserSavedSearch ? View.VISIBLE: View.GONE);
final TextView suggestionText = (TextView) v.findViewById(R.id.suggestion_text);
suggestionText.setText(suggestion);
setDescriptionOnSuggestion(suggestionText, suggestion);
@ -172,7 +178,76 @@ class SearchEngineRow extends AnimatedHeightLayout {
mEditSuggestionListener = listener;
}
public void updateFromSearchEngine(SearchEngine searchEngine, boolean animate) {
private void bindSuggestionView(String suggestion, boolean animate, int recycledSuggestionCount, Integer previousSuggestionChildIndex, boolean isUserSavedSearch){
final View suggestionItem;
// Reuse suggestion views from recycled view, if possible.
if (previousSuggestionChildIndex + 1 < recycledSuggestionCount) {
suggestionItem = mSuggestionView.getChildAt(previousSuggestionChildIndex + 1);
suggestionItem.setVisibility(View.VISIBLE);
} else {
suggestionItem = mInflater.inflate(R.layout.suggestion_item, null);
suggestionItem.setOnClickListener(mClickListener);
suggestionItem.setOnLongClickListener(mLongClickListener);
// Store the position of the suggestion for telemetry.
suggestionItem.setTag(String.valueOf(previousSuggestionChildIndex));
mSuggestionView.addView(suggestionItem);
}
setSuggestionOnView(suggestionItem, suggestion, isUserSavedSearch);
if (animate) {
AlphaAnimation anim = new AlphaAnimation(0, 1);
anim.setDuration(ANIMATION_DURATION);
anim.setStartOffset(previousSuggestionChildIndex * ANIMATION_DURATION);
suggestionItem.startAnimation(anim);
}
}
private void hideRecycledSuggestions(int lastVisibleChildIndex, int recycledSuggestionCount) {
// Hide extra suggestions that have been recycled.
for (int i = lastVisibleChildIndex + 1; i < recycledSuggestionCount; ++i) {
mSuggestionView.getChildAt(i).setVisibility(View.GONE);
}
}
private void updateFromSavedSearches(String searchTerm, boolean animate, int suggestionCounter, int recycledSuggestionCount) {
final ContentResolver cr = getContext().getContentResolver();
String[] columns = new String[] { SearchHistory.QUERY };
String sortOrderAndLimit = SearchHistory.DATE + " DESC";
final Cursor c = cr.query(SearchHistory.CONTENT_URI, columns, null, null, sortOrderAndLimit);
if (c == null) {
return;
}
try {
if (c.moveToFirst()) {
int counter = 0;
final int searchColumn = c.getColumnIndexOrThrow(SearchHistory.QUERY);
do {
final String savedSearch = c.getString(searchColumn);
if (counter == 4) {
break;
}
// Bug 1200371 will move the filtering/matching and limit into the sql query
if (savedSearch.startsWith(searchTerm)) {
bindSuggestionView(savedSearch, animate, recycledSuggestionCount, suggestionCounter, true);
++suggestionCounter;
++counter;
}
} while (c.moveToNext());
}
} finally {
c.close();
}
hideRecycledSuggestions(suggestionCounter, recycledSuggestionCount);
}
private int updateFromSearchEngine(SearchEngine searchEngine, boolean animate, int recycledSuggestionCount) {
// Update search engine reference.
mSearchEngine = searchEngine;
@ -182,54 +257,30 @@ class SearchEngineRow extends AnimatedHeightLayout {
// Set the initial content description.
setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString());
// Add additional suggestions given by this engine.
final int recycledSuggestionCount = mSuggestionView.getChildCount();
int suggestionCounter = 0;
// Apply Search Engine's suggestions
for (String suggestion : mSearchEngine.getSuggestions()) {
final View suggestionItem;
// Reuse suggestion views from recycled view, if possible.
if (suggestionCounter + 1 < recycledSuggestionCount) {
suggestionItem = mSuggestionView.getChildAt(suggestionCounter + 1);
suggestionItem.setVisibility(View.VISIBLE);
} else {
suggestionItem = mInflater.inflate(R.layout.suggestion_item, null);
suggestionItem.setOnClickListener(mClickListener);
suggestionItem.setOnLongClickListener(mLongClickListener);
// Store the position of the suggestion for telemetry.
suggestionItem.setTag(String.valueOf(suggestionCounter));
final ImageView magnifier =
(ImageView) suggestionItem.findViewById(R.id.suggestion_magnifier);
magnifier.setVisibility(View.GONE);
mSuggestionView.addView(suggestionItem);
}
setSuggestionOnView(suggestionItem, suggestion);
if (animate) {
AlphaAnimation anim = new AlphaAnimation(0, 1);
anim.setDuration(ANIMATION_DURATION);
anim.setStartOffset(suggestionCounter * ANIMATION_DURATION);
suggestionItem.startAnimation(anim);
}
bindSuggestionView(suggestion, animate, recycledSuggestionCount, suggestionCounter, false);
++suggestionCounter;
}
// Hide extra suggestions that have been recycled.
for (int i = suggestionCounter + 1; i < recycledSuggestionCount; ++i) {
mSuggestionView.getChildAt(i).setVisibility(View.GONE);
}
hideRecycledSuggestions(suggestionCounter, recycledSuggestionCount);
// Make sure mSelectedView is still valid.
if (mSelectedView >= mSuggestionView.getChildCount()) {
mSelectedView = mSuggestionView.getChildCount() - 1;
}
return suggestionCounter;
}
public void updateSuggestions(boolean suggestionsEnabled, SearchEngine searchEngine, String searchTerm, boolean animate) {
// This can be called before the opt-in permission prompt is shown or set. Check first.
if (suggestionsEnabled) {
final int recycledSuggestionCount = mSuggestionView.getChildCount();
final int suggestionViewCount = updateFromSearchEngine(searchEngine, animate, recycledSuggestionCount);
updateFromSavedSearches(searchTerm, animate, suggestionViewCount, recycledSuggestionCount);
}
}
@Override

View File

@ -44,18 +44,6 @@ chrome-%::
$(dir-res-raw)-$(AB_rCD)/browsersearch.json \
AB_CD=$*
# setup the path to bookmarks.inc. copied and tweaked version of MERGE_FILE from config/config.mk
MOBILE_LOCALE_SRCDIR = $(if $(filter en-US,$(AB_CD)),$(topsrcdir)/mobile/locales/en-US,$(or $(realpath $(L10NBASEDIR)),$(abspath $(L10NBASEDIR)))/$(AB_CD)/mobile)
ifdef LOCALE_MERGEDIR
BOOKMARKSPATH = $(firstword \
$(wildcard $(LOCALE_MERGEDIR)/mobile/profile/bookmarks.inc ) \
$(wildcard $(MOBILE_LOCALE_SRCDIR)/profile/bookmarks.inc ) \
$(topsrcdir)/mobile/locales/en-US/profile/bookmarks.inc )
else
BOOKMARKSPATH = $(abspath $(MOBILE_LOCALE_SRCDIR)/profile/bookmarks.inc)
endif
# Determine the ../res/values[-*]/ path
strings-xml-bypath = $(filter %/strings.xml,$(MAKECMDGOALS))
ifeq (,$(strip $(strings-xml-bypath)))
@ -69,7 +57,6 @@ strings-xml-preqs =\
$(STRINGSPATH) \
$(SEARCHSTRINGSPATH) \
$(SYNCSTRINGSPATH) \
$(BOOKMARKSPATH) \
$(if $(IS_LANGUAGE_REPACK),FORCE) \
$(NULL)
@ -80,7 +67,6 @@ $(dir-strings-xml)/strings.xml: $(strings-xml-preqs)
$(call py_action,preprocessor, \
$(DEFINES) \
-DANDROID_PACKAGE_NAME=$(ANDROID_PACKAGE_NAME) \
-DBOOKMARKSPATH='$(BOOKMARKSPATH)' \
-DBRANDPATH='$(BRANDPATH)' \
-DMOZ_ANDROID_SHARED_ACCOUNT_TYPE=$(MOZ_ANDROID_SHARED_ACCOUNT_TYPE) \
-DMOZ_ANDROID_SHARED_FXACCOUNT_TYPE=$(MOZ_ANDROID_SHARED_FXACCOUNT_TYPE) \

View File

@ -703,3 +703,18 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!ENTITY restriction_disallow_master_password_title2 "Disable master password">
<!ENTITY restriction_disallow_guest_browsing_title2 "Disable Guest Browsing">
<!-- Default Bookmarks titles-->
<!-- LOCALIZATION NOTE (bookmarks_title): title for the folder that will contains the default bookmarks -->
<!ENTITY bookmarks_title "Mobile">
<!-- LOCALIZATION NOTE (bookmarks_about_browser): link title for about:fennec -->
<!ENTITY bookmarks_about_browser "Firefox: About your browser">
<!-- LOCALIZATION NOTE (bookmarks_addons): link title for https://addons.mozilla.org/en-US/mobile -->
<!ENTITY bookmarks_addons "Firefox: Customize with add-ons">
<!-- LOCALIZATION NOTE (bookmarks_support): link title for https://support.mozilla.org/ -->
<!ENTITY bookmarks_support "Firefox: Support">
<!--LOCALIZATION NOTE (bookmarks_marketplace):link title for https://marketplace.firefox.com -->
<!ENTITY bookmarks_marketplace "Firefox Marketplace">
<!-- LOCALIZATION NOTE (bookmarks_restricted_support): link title for https://support.mozilla.org/kb/kids -->
<!ENTITY bookmarks_restricted_support "Firefox Help and Support for a simplified kid-friendly version of Firefox">
<!-- LOCALIZATION NOTE (bookmarks_restricted_webmaker):link title for https://webmaker.org -->
<!ENTITY bookmarks_restricted_webmaker "Learn the Web: Mozilla Webmaker">

View File

@ -12,11 +12,12 @@
android:clickable="true"
android:padding="7dp">
<ImageView android:id="@+id/suggestion_magnifier"
android:src="@drawable/search_icon_inactive"
<ImageView android:id="@+id/suggestion_item_icon"
android:src="@drawable/icon_most_recent_empty"
android:layout_marginRight="3dip"
android:layout_width="16dip"
android:layout_height="16dip"/>
android:layout_width="18dip"
android:layout_height="18dip"
android:visibility="gone"/>
<TextView android:id="@+id/suggestion_text"
android:layout_width="wrap_content"

View File

@ -19,7 +19,6 @@
<!ENTITY formatD "&#037;d">
]>
#includesubst @BOOKMARKSPATH@
<resources>
<string name="moz_app_displayname">@MOZ_APP_DISPLAYNAME@</string>
<string name="android_package_name">@ANDROID_PACKAGE_NAME@</string>
@ -453,25 +452,25 @@
<!-- Default bookmarks. We used to use bookmark titles shared with XUL from mobile's
profile/bookmarks.inc (see bug 964946). Don't expose the URLs to L10N. -->
<string name="bookmarkdefaults_title_aboutfirefox">@bookmarks_aboutBrowser@</string>
<string name="bookmarkdefaults_title_aboutfirefox">&bookmarks_about_browser;</string>
<string name="bookmarkdefaults_url_aboutfirefox">about:firefox</string>
<!-- Icon is automatically generated from R.drawable.bookmarkdefaults_favicon_addons -->
<string name="bookmarkdefaults_title_addons">@bookmarks_addons@</string>
<string name="bookmarkdefaults_title_addons">&bookmarks_addons;</string>
<string name="bookmarkdefaults_url_addons">https://addons.mozilla.org/android/</string>
<!-- Icon is automatically generated from R.drawable.bookmarkdefaults_favicon_support -->
<string name="bookmarkdefaults_title_support">@bookmarks_support@</string>
<string name="bookmarkdefaults_title_support">&bookmarks_support;</string>
<string name="bookmarkdefaults_url_support">https://support.mozilla.org/products/mobile</string>
<!-- Icon is automatically generated from R.drawable.bookmarkdefaults_favicon_marketplace -->
<string name="bookmarkdefaults_title_marketplace">@bookmarks_marketplace@</string>
<string name="bookmarkdefaults_title_marketplace">&bookmarks_marketplace;</string>
<string name="bookmarkdefaults_url_marketplace">https://marketplace.firefox.com/</string>
<string name="bookmarkdefaults_title_restricted_webmaker">@bookmarks_restricted_webmaker@</string>
<string name="bookmarkdefaults_title_restricted_webmaker">&bookmarks_restricted_webmaker;</string>
<string name="bookmarkdefaults_url_restricted_webmaker">https://webmaker.org/</string>
<string name="bookmarkdefaults_title_restricted_support">@bookmarks_restricted_support@</string>
<string name="bookmarkdefaults_title_restricted_support">&bookmarks_restricted_support;</string>
<string name="bookmarkdefaults_url_restricted_support">https://support.mozilla.org/kb/kids</string>
<!-- Site identity popup -->

View File

@ -44,17 +44,6 @@ search-jar-default: search-jar
###########################################################################
bookmarks = bookmarks.json
bookmarks-ts = $(tgt-gendir)/$(bookmarks)
src-bookmarks = $(srcdir)/generic/profile/$(bookmarks).in
GARBAGE += $(bookmarks) $(bookmarks-ts)
# ---------------------------------------------------------------------------
# Note: Always symlink bookmarks.json to pickup the current build for a
# locale. Phase 2 edits should remove the common/symlink file and
# provide a user function able to derive the path.
###########################################################################
## Searchlist plugin config
plugin-file-array = \
$(wildcard $(LOCALE_SRCDIR)/searchplugins/list.txt) \
@ -144,7 +133,6 @@ libs-%: $(libs-preqs)
$(display-deps)
@$(MAKE) -C $(DEPTH)/toolkit/locales libs-$*
@$(MAKE) -C $(DEPTH)/intl/locales AB_CD=$* XPI_NAME=locale-$*
@$(MAKE) -B $(bookmarks) AB_CD=$*
@$(MAKE) -B searchplugins AB_CD=$* XPI_NAME=locale-$*
@$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=defaults/pref
@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
@ -152,7 +140,6 @@ libs-%: $(libs-preqs)
# Tailored target to just add the chrome processing for multi-locale builds
chrome-%:
$(display-deps)
@$(MAKE) -B $(bookmarks) AB_CD=$*
@$(MAKE) -B searchplugins AB_CD=$*
@$(MAKE) chrome AB_CD=$*
@$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales chrome AB_CD=$*
@ -160,37 +147,4 @@ chrome-%:
NO_JA_JP_MAC_AB_CD := $(if $(filter ja-JP-mac, $(AB_CD)),ja,$(AB_CD))
# emulate vpath to gather deps with a path
has-mergedir = $(if $(strip $(LOCALE_MERGEDIR)),1)
bookmarks-inc-array = \
$(wildcard \
$(if $(has_mergedir),$(LOCALE_MERGEDIR)/mobile/profile/bookmarks.inc) \
$(LOCALE_SRCDIR)/profile/bookmarks.inc \
$(if $(has-mergedir),$(srcdir)/en-US/profile/bookmarks.inc) \
)
bookmarks-inc = $(firstword $(bookmarks-inc-array))
bookmarks-preqs = \
$(bookmarks-inc) \
$(src-bookmarks) \
generic/profile/$(bookmarks).in \
$(if $(IS_LANGUAGE_REPACK),FORCE) \
$(GLOBAL_DEPS) \
$(NULL)
$(bookmarks-ts): $(bookmarks-preqs)
$(display_deps)
$(call py_action,preprocessor, \
-I $< \
-DAB_CD=$(NO_JA_JP_MAC_AB_CD) \
$(src-bookmarks) \
-o $@)
.PHONY: bookmarks $(bookmarks)
bookmarks: $(bookmarks)
$(bookmarks): $(bookmarks-ts)
@echo '\nGenerating: $@'
ln -fn $< .
export:: searchplugins bookmarks
export:: searchplugins

View File

@ -1,41 +0,0 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#filter emptyLines
# LOCALIZATION NOTE: The 'en-US' strings in the URLs will be replaced with
# your locale code, and link to your translated pages as soon as they're
# live.
# LOCALIZATION NOTE: Some of these URLs are currently 404s, but should be coming
# online shortly.
# LOCALIZATION NOTE (bookmarks_title):
# title for the folder that will contains the default bookmarks
#define bookmarks_title Mobile
# LOCALIZATION NOTE (bookmarks_aboutBrowser):
# link title for about:fennec
#define bookmarks_aboutBrowser Firefox: About your browser
# LOCALIZATION NOTE (bookmarks_addons):
# link title for https://addons.mozilla.org/en-US/mobile
#define bookmarks_addons Firefox: Customize with add-ons
# LOCALIZATION NOTE (bookmarks_support):
# link title for https://support.mozilla.org/
#define bookmarks_support Firefox: Support
# LOCALIZATION NOTE (bookmarks_marketplace):
# link title for https://marketplace.firefox.com
#define bookmarks_marketplace Firefox Marketplace
# LOCALIZATION NOTE (bookmarks_restricted_support):
# link title for https://support.mozilla.org/kb/kids
#define bookmarks_restricted_support Firefox Help and Support for a simplified kid-friendly version of Firefox
# LOCALIZATION NOTE (bookmarks_restricted_webmaker):
# link title for https://webmaker.org
#define bookmarks_restricted_webmaker Learn the Web: Mozilla Webmaker
#unfilter emptyLines

View File

@ -1,8 +0,0 @@
#filter substitution
{"type":"text/x-moz-place-container","root":"placesRoot","children":
[{"type":"text/x-moz-place-container","title":"@bookmarks_title@","annos":[{"name":"mobile/bookmarksRoot","expires":4,"type":1,"value":1}],
# Bug 921433: this is empty, because bookmarks are now added via resource
# strings: see LocalBrowserDB.addDefaultBookmarks.
"children": []
}]
}

View File

@ -221,7 +221,7 @@ this.FxAccountsManager = {
}
);
}
return Promise.reject(reason);
return Promise.reject(reason.message ? { error: reason.message } : reason);
},
_getAssertion: function(aAudience, aPrincipal) {

View File

@ -396,6 +396,7 @@ add_test(function() {
do_throw("Unexpected success");
},
error => {
do_check_eq(error.error, ERROR_OFFLINE);
FxAccountsManager._fxAccounts._reset();
Services.io.offline = false;
certExpired = false;

View File

@ -476,7 +476,9 @@ var LoginManagerContent = {
// If we have a target input, fills it's form.
if (inputElement) {
form = FormLikeFactory.createFromField(inputElement);
clobberUsername = false;
if (inputElement.type == "password") {
clobberUsername = false;
}
}
this._fillForm(form, true, clobberUsername, true, true, loginsFound, recipes, options);
},
@ -812,8 +814,11 @@ var LoginManagerContent = {
*
* @param {HTMLFormElement} form
* @param {bool} autofillForm denotes if we should fill the form in automatically
* @param {bool} clobberUsername controls if an existing username can be
* overwritten
* @param {bool} clobberUsername controls if an existing username can be overwritten.
* If this is false and an inputElement of type password
* is also passed, the username field will be ignored.
* If this is false and no inputElement is passed, if the username
* field value is not found in foundLogins, it will not fill the password.
* @param {bool} clobberPassword controls if an existing password value can be
* overwritten
* @param {bool} userTriggered is an indication of whether this filling was triggered by
@ -867,11 +872,16 @@ var LoginManagerContent = {
// the same as the one heuristically found, use the parameter
// one instead.
if (inputElement) {
if (inputElement.type != "password") {
if (inputElement.type == "password") {
passwordField = inputElement;
if (!clobberUsername) {
usernameField = null;
}
} else if (LoginHelper.isUsernameFieldType(inputElement)) {
usernameField = inputElement;
} else {
throw new Error("Unexpected input element type.");
}
passwordField = inputElement;
usernameField = null;
}
// Need a valid password field to do anything.
@ -1031,6 +1041,53 @@ var LoginManagerContent = {
}
},
/**
* Verify if a field is a valid login form field and
* returns some information about it's FormLike.
*
* @param {Element} aField
* A form field we want to verify.
*
* @returns {Object} an object with information about the
* FormLike username and password field
* or null if the passed field is invalid.
*/
getFieldContext(aField) {
// If the element is not a proper form field, return null.
if (!(aField instanceof Ci.nsIDOMHTMLInputElement) ||
(aField.type != "password" && !LoginHelper.isUsernameFieldType(aField)) ||
!aField.ownerDocument) {
return null;
}
let form = FormLikeFactory.createFromField(aField);
let doc = aField.ownerDocument;
let messageManager = messageManagerFromWindow(doc.defaultView);
let recipes = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
formOrigin: LoginUtils._getPasswordOrigin(doc.documentURI),
})[0];
let [usernameField, newPasswordField, oldPasswordField] =
this._getFormFields(form, false, recipes);
// If we are not verifying a password field, we want
// to use aField as the username field.
if (aField.type != "password") {
usernameField = aField;
}
return {
usernameField: {
found: !!usernameField,
disabled: usernameField && (usernameField.disabled || usernameField.readOnly),
},
passwordField: {
found: !!newPasswordField,
disabled: newPasswordField && (newPasswordField.disabled || newPasswordField.readOnly),
},
};
},
};
var LoginUtils = {

View File

@ -60,7 +60,7 @@ let LoginManagerContextMenu = {
// login is bound so we can keep the reference to each object.
item.addEventListener("command", function(login, event) {
this._fillPassword(login, inputElement, browser, documentURI);
this._fillTargetField(login, inputElement, browser, documentURI);
}.bind(this, login));
fragment.appendChild(item);
@ -151,7 +151,7 @@ let LoginManagerContextMenu = {
* This isn't the same as the browser's top-level
* document URI when subframes are involved.
*/
_fillPassword(login, inputElement, browser, documentURI) {
_fillTargetField(login, inputElement, browser, documentURI) {
LoginManagerParent.fillForm({
browser: browser,
loginFormOrigin: documentURI.prePath,

View File

@ -8,6 +8,7 @@ Cu.import("resource://testing-common/LoginTestUtils.jsm", this);
// The hostname for the test URIs.
const TEST_HOSTNAME = "https://example.com";
const MULTIPLE_FORMS_PAGE_PATH = "/browser/toolkit/components/passwordmgr/test/browser/multiple_forms.html";
/**
* Initialize logins needed for the tests and disable autofill
@ -28,13 +29,35 @@ add_task(function* test_initialize() {
* Check if the context menu is populated with the right
* menuitems for the target password input field.
*/
add_task(function* test_context_menu_populate() {
add_task(function* test_context_menu_populate_password() {
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
}, function* (browser) {
let passwordInput = browser.contentWindow.document.getElementById("test-password-1");
yield openPasswordContextMenu(browser, passwordInput);
// Check the content of the password manager popup
let popupMenu = document.getElementById("fill-login-popup");
checkMenu(popupMenu);
let contextMenu = document.getElementById("contentAreaContextMenu");
contextMenu.hidePopup();
});
});
/**
* Check if the context menu is populated with the right menuitems
* for the target username field with a password field present.
*/
add_task(function* test_context_menu_populate_username_with_password() {
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + "/browser/toolkit/components/" +
"passwordmgr/test/browser/multiple_forms.html",
}, function* (browser) {
let passwordInput = browser.contentWindow.document.getElementById("test-password-1");
let passwordInput = browser.contentWindow.document.getElementById("test-username-2");
yield openPasswordContextMenu(browser, passwordInput);
@ -52,87 +75,104 @@ add_task(function* test_context_menu_populate() {
* login menuitem is clicked.
*/
add_task(function* test_context_menu_password_fill() {
// Set of element ids to check.
let testSet = [
{
passwordInput: "test-password-1",
unchangedFields: null,
},
{
passwordInput: "test-password-2",
unchangedFields: ["test-username-2"],
},
{
passwordInput: "test-password-3",
unchangedFields: ["test-username-3"],
},
{
passwordInput: "test-password-4",
unchangedFields: ["test-username-4"],
},
{
passwordInput: "test-password-5",
unchangedFields: ["test-username-5", "test-password2-5"],
},
{
passwordInput: "test-password2-5",
unchangedFields: ["test-username-5", "test-password-5"],
},
{
passwordInput: "test-password-6",
unchangedFields: ["test-username-6", "test-password2-6"],
},
{
passwordInput: "test-password2-6",
unchangedFields: ["test-username-6", "test-password-6"],
},
{
passwordInput: "test-password-7",
unchangedFields: null,
},
];
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + "/browser/toolkit/components/" +
"passwordmgr/test/browser/multiple_forms.html",
url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
}, function* (browser) {
for (let testCase of testSet) {
let passwordInput = browser.contentWindow.document.getElementById(testCase.passwordInput);
yield openPasswordContextMenu(browser, passwordInput);
let testForms = browser.contentWindow.document.getElementsByClassName("test-form");
for (let form of testForms) {
let usernameInputList = form.querySelectorAll("input[type='password']");
info("Testing form: " + form.getAttribute("description"));
let popupMenu = document.getElementById("fill-login-popup");
for (let passwordField of usernameInputList) {
info("Testing password field: " + passwordField.id);
// Store the values of fields that should remain unchanged.
let unchangedFieldsValues = null;
if (testCase.unchangedFields) {
unchangedFieldsValues = [];
for (let fieldId of testCase.unchangedFields) {
unchangedFieldsValues[fieldId] = browser.contentWindow.document.getElementById(fieldId).value;
let contextMenu = document.getElementById("contentAreaContextMenu");
let menuItemStatus = form.getAttribute("menuitemStatus");
// Synthesize a right mouse click over the username input element.
yield openPasswordContextMenu(browser, passwordField, ()=> {
let popupHeader = document.getElementById("fill-login");
// If the password field is disabled or read-only, we want to see
// the disabled Fill Password popup header.
if (passwordField.disabled || passwordField.readOnly) {
Assert.ok(!popupHeader.hidden, "Popup menu is not hidden.");
Assert.ok(popupHeader.disabled, "Popup menu is disabled.");
contextMenu.hidePopup();
return false;
}
return true;
});
if (contextMenu.state != "open") {
continue;
}
// The only field affected by the password fill
// should be the target password field itself.
let unchangedFields = form.querySelectorAll('input:not(#' + passwordField.id + ')');
yield assertContextMenuFill(form, null, passwordField, unchangedFields);
contextMenu.hidePopup();
}
}
});
});
// Execute the default command of the first login menuitem found at the context menu.
let firstLoginItem = popupMenu.getElementsByClassName("context-login-item")[0];
firstLoginItem.doCommand();
/**
* Check if the form is correctly filled when one
* username context menu login menuitem is clicked.
*/
add_task(function* test_context_menu_username_login_fill() {
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
}, function* (browser) {
yield BrowserTestUtils.waitForEvent(passwordInput, "input", "Password input value changed");
let testForms = browser.contentWindow.document.getElementsByClassName("test-form");
for (let form of testForms) {
let usernameInputList = form.querySelectorAll("input[type='text']");
info("Testing form: " + form.getAttribute("description"));
// Find the used login by it's username (Use only unique usernames in this test).
let login = getLoginFromUsername(firstLoginItem.label);
for (let usernameField of usernameInputList) {
info("Testing username field: " + usernameField.id);
Assert.equal(login.password, passwordInput.value, "Password filled and correct.");
// We always want to check if the first password field is filled,
// since this is the current behavior from the _fillForm function.
let passwordField = form.querySelector("input[type='password']");
// Check that the fields that should remain unchanged didn't got modified.
if (testCase.unchangedFields) {
Assert.ok(testCase.unchangedFields.every(fieldId => {
return unchangedFieldsValues[fieldId] == browser.contentWindow.document.getElementById(fieldId).value;
}), "Other fields were not changed.");
let contextMenu = document.getElementById("contentAreaContextMenu");
let menuItemStatus = form.getAttribute("menuitemStatus");
// Synthesize a right mouse click over the username input element.
yield openPasswordContextMenu(browser, usernameField, ()=> {
let popupHeader = document.getElementById("fill-login");
// If we don't want to see the actual popup menu,
// check if the popup is hidden or disabled.
if (!passwordField || usernameField.disabled || usernameField.readOnly ||
passwordField.disabled || passwordField.readOnly) {
if (!passwordField) {
Assert.ok(popupHeader.hidden, "Popup menu is hidden.");
} else {
Assert.ok(!popupHeader.hidden, "Popup menu is not hidden.");
Assert.ok(popupHeader.disabled, "Popup menu is disabled.");
}
contextMenu.hidePopup();
return false;
}
return true;
});
if (contextMenu.state != "open") {
continue;
}
// We shouldn't change any field that's not the target username field or the first password field
let unchangedFields = form.querySelectorAll('input:not(#' + usernameField.id + '):not(#' + passwordField.id + ')');
yield assertContextMenuFill(form, usernameField, passwordField, unchangedFields);
contextMenu.hidePopup();
}
let contextMenu = document.getElementById("contentAreaContextMenu");
contextMenu.hidePopup();
}
});
});
@ -143,8 +183,7 @@ add_task(function* test_context_menu_password_fill() {
add_task(function* test_context_menu_iframe_fill() {
yield BrowserTestUtils.withNewTab({
gBrowser,
url: TEST_HOSTNAME + "/browser/toolkit/components/" +
"passwordmgr/test/browser/multiple_forms.html",
url: TEST_HOSTNAME + MULTIPLE_FORMS_PAGE_PATH,
}, function* (browser) {
let iframe = browser.contentWindow.document.getElementById("test-iframe");
let passwordInput = iframe.contentDocument.getElementById("form-basic-password");
@ -199,14 +238,22 @@ add_task(function* test_context_menu_iframe_fill() {
/**
* Synthesize mouse clicks to open the password manager context menu popup
* for a target password input element.
*
* assertCallback should return true if we should continue or else false.
*/
function* openPasswordContextMenu(browser, passwordInput) {
function* openPasswordContextMenu(browser, passwordInput, assertCallback = null) {
// Synthesize a right mouse click over the password input element.
let contextMenuShownPromise = BrowserTestUtils.waitForEvent(window, "popupshown");
let eventDetails = {type: "contextmenu", button: 2};
BrowserTestUtils.synthesizeMouseAtCenter(passwordInput, eventDetails, browser);
yield contextMenuShownPromise;
if (assertCallback) {
if (!assertCallback.call()) {
return;
}
}
// Synthesize a mouse click over the fill login menu header.
let popupHeader = document.getElementById("fill-login");
let popupShownPromise = BrowserTestUtils.waitForEvent(popupHeader, "popupshown");
@ -214,6 +261,51 @@ function* openPasswordContextMenu(browser, passwordInput) {
yield popupShownPromise;
}
/**
* Verify that only the expected form fields are filled.
*/
function* assertContextMenuFill(form, usernameField, passwordField, unchangedFields){
let popupMenu = document.getElementById("fill-login-popup");
// Store the value of fields that should remain unchanged.
if (unchangedFields.length) {
for (let field of unchangedFields) {
field.setAttribute("original-value", field.value);
}
}
// Execute the default command of the first login menuitem found at the context menu.
let firstLoginItem = popupMenu.getElementsByClassName("context-login-item")[0];
firstLoginItem.doCommand();
yield BrowserTestUtils.waitForEvent(form, "input", "Username input value changed");
// Find the used login by it's username (Use only unique usernames in this test).
let login = getLoginFromUsername(firstLoginItem.label);
// If we have an username field, check if it's correctly filled
if (usernameField && usernameField.getAttribute("expectedFail") == null) {
Assert.equal(login.username, usernameField.value, "Username filled and correct.");
}
// If we have a password field, check if it's correctly filled
if (passwordField && passwordField.getAttribute("expectedFail") == null) {
Assert.equal(passwordField.value, login.password, "Password filled and correct.");
}
// Check that all fields that should not change have the same value as before.
if (unchangedFields.length) {
Assert.ok(()=> {
for (let field of unchangedFields) {
if (field.value != field.getAttribute("original-value")) {
return false;
}
}
return true;
}, "Other fields were not changed.");
}
}
/**
* Check if every login that matches the page hostname are available at the context menu.
*/

View File

@ -2,54 +2,126 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!-- Password only form -->
<form id='form-1'>
<form class="test-form"
description="Password only form">
<input id='test-password-1' type='password' name='pname' value=''>
<input type='submit'>Submit</input>
</form>
<!-- Simple username and password blank form -->
<form id='form-2'>
<form class="test-form"
description="Username only form">
<input id='test-username-1' type='text' name='uname' value=''>
<input type='submit'>Submit</input>
</form>
<form class="test-form"
description="Simple username and password blank form">
<input id='test-username-2' type='text' name='uname' value=''>
<input id='test-password-2' type='password' name='pname' value=''>
<button type='submit'>Submit</button>
</form>
<!-- Simple username and password form, prefilled username -->
<form id='form-3'>
<form class="test-form"
description="Simple username and password form, prefilled username">
<input id='test-username-3' type='text' name='uname' value='testuser'>
<input id='test-password-3' type='password' name='pname' value=''>
<button type='submit'>Submit</button>
</form>
<!-- Simple username and password form, prefilled username and password -->
<form id='form-4'>
<form class="test-form"
description="Simple username and password form, prefilled username and password">
<input id='test-username-4' type='text' name='uname' value='testuser'>
<input id='test-password-4' type='password' name='pname' value='testpass'>
<button type='submit'>Submit</button>
</form>
<!-- One username and two passwords empty form -->
<form id='form-5'>
<form class="test-form"
description="One username and two passwords empty form">
<input id='test-username-5' type='text' name='uname'>
<input id='test-password-5' type='password' name='pname'>
<input id='test-password2-5' type='password' name='pname2'>
<button type='submit'>Submit</button>
</form>
<!-- One username and two passwords form, fields prefiled -->
<form id='form-6'>
<form class="test-form"
description="One username and two passwords form, fields prefiled">
<input id='test-username-6' type='text' name='uname' value="testuser">
<input id='test-password-6' type='password' name='pname' value="testpass">
<input id='test-password2-6' type='password' name='pname2' value="testpass">
<button type='submit'>Submit</button>
</form>
<!-- Password field with no form -->
<div id='form-7'>
<div class="test-form"
description="Username and password fields with no form">
<input id='test-username-7' type='text' name='uname' value="testuser">
<input id='test-password-7' type='password' name='pname' value="testpass">
</div>
<form class="test-form"
description="Simple username and password blank form, with disabled password">
<input id='test-username-8' type='text' name='uname' value=''>
<input id='test-password-8' type='password' name='pname' value='' disabled>
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="Simple username and password blank form, with disabled username">
<input id='test-username-9' type='text' name='uname' value='' disabled>
<input id='test-password-9' type='password' name='pname' value=''>
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="Simple username and password blank form, with readonly password">
<input id='test-username-10' type='text' name='uname' value=''>
<input id='test-password-10' type='password' name='pname' value='' readonly>
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="Simple username and password blank form, with readonly username">
<input id='test-username-11' type='text' name='uname' value='' readonly>
<input id='test-password-11' type='password' name='pname' value=''>
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="Two username and one passwords form, fields prefiled">
<input id='test-username-12' type='text' name='uname' value="testuser">
<input id='test-username2-12' type='text' name='uname2' value="testuser">
<input id='test-password-12' type='password' name='pname' value="testpass">
<button type='submit'>Submit</button>
</form>
<form class="test-form"
description="Two username and one passwords form, one disabled username field">
<input id='test-username-13' type='text' name='uname'>
<input id='test-username2-13' type='text' name='uname2' disabled>
<input id='test-password-13' type='password' name='pname'>
<button type='submit'>Submit</button>
</form>
<div class="test-form"
description="Second username and password fields with no form">
<input id='test-username-14' type='text' name='uname'>
<input id='test-password-14' type='password' name='pname' expectedFail>
</div>
<!-- Form in an iframe -->
<iframe src="https://example.org/browser/toolkit/components/passwordmgr/test/browser/form_basic.html" id="test-iframe"></iframe>

View File

@ -24,12 +24,14 @@ XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
Ci.nsITelemetry);
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
const FILTERS = [
{probe: "jank", field: "longestDuration"},
{probe: "cpow", field: "totalCPOWTime"},
];
const WAKEUP_IS_SURPRISINGLY_SLOW_FACTOR = 2;
const THREAD_TAKES_LOTS_OF_CPU_FACTOR = .75;
let AddonWatcher = {
_previousPerformanceIndicators: {},
@ -52,6 +54,15 @@ let AddonWatcher = {
*/
_interval: 15000,
_ignoreList: null,
/**
* The date of the latest wakeup, in milliseconds since an arbitrary
* epoch.
*
* @type {number}
*/
_latestWakeup: Date.now(),
/**
* Initialize and launch the AddonWatcher.
*
@ -130,6 +141,49 @@ let AddonWatcher = {
},
_isPaused: true,
/**
* @return {true} If any measure we have for this wakeup is invalid
* because the system is very busy and/or coming backup from hibernation.
*/
_isSystemTooBusy: function(deltaT, currentSnapshot, previousSnapshot) {
if (deltaT <= WAKEUP_IS_SURPRISINGLY_SLOW_FACTOR * this._interval) {
// The wakeup was reasonably accurate.
return false;
}
// There has been a strangely long delay between two successive
// wakeups. This can mean one of the following things:
// 1. we're in the process of initializing the app;
// 2. the system is not responsive, either because it is very busy
// or because it has gone to sleep;
// 3. the main loop of the application is so clogged that it could
// not process timer events.
//
// In cases 1. or 2., any alert here is a false positive.
// In case 3., the application (hopefully an add-on) is misbehaving and we need
// to identify what's wrong.
if (!previousSnapshot) {
// We're initializing, skip.
return true;
}
let diff = snapshot.processData.subtract(previousSnapshot.processData);
if (diff.totalCPUTime >= deltaT * THREAD_TAKES_LOTS_OF_CPU_FACTOR ) {
// The main thread itself is using lots of CPU, perhaps because of
// an add-on. We need to investigate.
//
// Note that any measurement based on wallclock time may
// be affected by the lack of responsiveness of the main event loop,
// so we may end up with false positives along the way.
return false;
}
// The application is apparently behaving correctly, so the issue must
// be somehow due to the system.
return true;
},
/**
* Check the performance of add-ons during the latest slice of time.
*
@ -139,9 +193,14 @@ let AddonWatcher = {
* slice.
*/
_checkAddons: function() {
let previousWakeup = this._latestWakeup;
let currentWakeup = this._latestWakeup = Date.now();
return Task.spawn(function*() {
try {
let snapshot = yield this._monitor.promiseSnapshot();
let previousSnapshot = this._latestSnapshot; // FIXME: Implement
let snapshot = this._latestSnapshot = yield this._monitor.promiseSnapshot();
let isSystemTooBusy = this._isSystemTooBusy(currentWakeup - previousWakeup, snapshot, previousSnapshot);
let limits = {
// By default, warn if we have a total time of 1s of CPOW per 15 seconds
@ -173,12 +232,18 @@ let AddonWatcher = {
if (!previous) {
// This is the first time we see the addon, so we are probably
// executed right during/after startup. Performance is always
// executed right during/just after its startup. Performance is always
// weird during startup, with the JIT warming up, competition
// in disk access, etc. so we do not take this as a reason to
// display the slow addon warning.
continue;
}
if (isSystemTooBusy) {
// The main event loop is behaving weirdly, most likely because of
// the system being busy or asleep, so results are not trustworthy.
// Ignore.
continue;
}
// Report misbehaviors to Telemetry

View File

@ -57,7 +57,8 @@ function CssColor(colorValue) {
module.exports.colorUtils = {
CssColor: CssColor,
rgbToHsl: rgbToHsl,
setAlpha: setAlpha
setAlpha: setAlpha,
classifyColor: classifyColor
};
/**
@ -74,7 +75,10 @@ CssColor.COLORUNIT = {
CssColor.prototype = {
_colorUnit: null,
// The value as-authored.
authored: null,
// A lower-cased copy of |authored|.
lowerCased: null,
get colorUnit() {
if (this._colorUnit === null) {
@ -112,7 +116,7 @@ CssColor.prototype = {
},
get specialValue() {
return SPECIALVALUES.has(this.authored) ? this.authored : null;
return SPECIALVALUES.has(this.lowerCased) ? this.authored : null;
},
get name() {
@ -171,8 +175,8 @@ CssColor.prototype = {
return invalidOrSpecialValue;
}
if (!this.hasAlpha) {
if (this.authored.startsWith("rgb(")) {
// The color is valid and begins with rgb(. Return the authored value.
if (this.lowerCased.startsWith("rgb(")) {
// The color is valid and begins with rgb(.
return this.authored;
}
let tuple = this._getRGBATuple();
@ -186,8 +190,8 @@ CssColor.prototype = {
if (invalidOrSpecialValue !== false) {
return invalidOrSpecialValue;
}
if (this.authored.startsWith("rgba(")) {
// The color is valid and begins with rgba(. Return the authored value.
if (this.lowerCased.startsWith("rgba(")) {
// The color is valid and begins with rgba(.
return this.authored;
}
let components = this._getRGBATuple();
@ -202,8 +206,8 @@ CssColor.prototype = {
if (invalidOrSpecialValue !== false) {
return invalidOrSpecialValue;
}
if (this.authored.startsWith("hsl(")) {
// The color is valid and begins with hsl(. Return the authored value.
if (this.lowerCased.startsWith("hsl(")) {
// The color is valid and begins with hsl(.
return this.authored;
}
if (this.hasAlpha) {
@ -217,8 +221,8 @@ CssColor.prototype = {
if (invalidOrSpecialValue !== false) {
return invalidOrSpecialValue;
}
if (this.authored.startsWith("hsla(")) {
// The color is valid and begins with hsla(. Return the authored value.
if (this.lowerCased.startsWith("hsla(")) {
// The color is valid and begins with hsla(.
return this.authored;
}
if (this.hasAlpha) {
@ -256,7 +260,11 @@ CssColor.prototype = {
* Any valid color string
*/
newColor: function(color) {
this.authored = color.toLowerCase();
// Store a lower-cased version of the color to help with format
// testing. The original text is kept as well so it can be
// returned when needed.
this.lowerCased = color.toLowerCase();
this.authored = color;
return this;
},
@ -319,7 +327,7 @@ CssColor.prototype = {
},
_hsl: function(maybeAlpha) {
if (this.authored.startsWith("hsl(") && maybeAlpha === undefined) {
if (this.lowerCased.startsWith("hsl(") && maybeAlpha === undefined) {
// We can use it as-is.
return this.authored;
}
@ -415,6 +423,27 @@ function setAlpha(colorValue, alpha) {
return "rgba(" + r + ", " + g + ", " + b + ", " + alpha + ")";
}
/**
* Given a color, classify its type as one of the possible color
* units, as known by |CssColor.colorUnit|.
*
* @param {String} value
* The color, in any form accepted by CSS.
* @return {String}
* The color classification, one of "rgb", "hsl", "hex", or "name".
*/
function classifyColor(value) {
value = value.toLowerCase();
if (value.startsWith("rgb(") || value.startsWith("rgba(")) {
return CssColor.COLORUNIT.rgb;
} else if (value.startsWith("hsl(") || value.startsWith("hsla(")) {
return CssColor.COLORUNIT.hsl;
} else if (/^#[0-9a-f]+$/.exec(value)) {
return CssColor.COLORUNIT.hex;
}
return CssColor.COLORUNIT.name;
}
loader.lazyGetter(this, "DOMUtils", function () {
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
});

View File

@ -241,5 +241,9 @@ exports.PromisesFront = protocol.FrontClass(PromisesActor, {
protocol.Front.prototype.initialize.call(this, client, form);
this.actorID = form.promisesActor;
this.manage(this);
},
destroy: function() {
protocol.Front.prototype.destroy.call(this);
}
});

View File

@ -0,0 +1,29 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Test classifyColor.
"use strict";
const {colorUtils} = require("devtools/css-color");
const CLASSIFY_TESTS = [
{ input: "rgb(255,0,192)", output: "rgb" },
{ input: "RGB(255,0,192)", output: "rgb" },
{ input: "rgba(255,0,192, 0.25)", output: "rgb" },
{ input: "hsl(5, 5, 5)", output: "hsl" },
{ input: "hsla(5, 5, 5, 0.25)", output: "hsl" },
{ input: "hSlA(5, 5, 5, 0.25)", output: "hsl" },
{ input: "#f0c", output: "hex" },
{ input: "#fe01cb", output: "hex" },
{ input: "#FE01CB", output: "hex" },
{ input: "blue", output: "name" },
{ input: "orange", output: "name" }
];
function run_test() {
for (let test of CLASSIFY_TESTS) {
let result = colorUtils.classifyColor(test.input);
equal(result, test.output, "test classifyColor(" + test.input + ")");
}
}

View File

@ -16,6 +16,7 @@ support-files =
[test_defineLazyPrototypeGetter.js]
[test_async-utils.js]
[test_consoleID.js]
[test_cssColor.js]
[test_prettifyCSS.js]
[test_require_lazy.js]
[test_require.js]