зеркало из https://github.com/mozilla/gecko-dev.git
Merge mozilla-central to mozilla-inbound
This commit is contained in:
Коммит
309a1a17da
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -26,7 +26,7 @@ function DebuggerPanel(iframeWindow, toolbox) {
|
|||
this.unhighlightWhenResumed = this.unhighlightWhenResumed.bind(this);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
};
|
||||
}
|
||||
|
||||
exports.DebuggerPanel = DebuggerPanel;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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");
|
||||
});
|
|
@ -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");
|
||||
});
|
|
@ -7,5 +7,4 @@
|
|||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
||||
EXTRA_JS_MODULES.devtools.promisedebugger += [
|
||||
'promise-debugger.js'
|
||||
]
|
||||
|
|
|
@ -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);
|
|
@ -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";
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'});
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -25775,7 +25775,7 @@ dialectic/SM
|
|||
dialectical
|
||||
dialectics/M
|
||||
dialing/S
|
||||
dialog/DG
|
||||
dialog/DGS
|
||||
dialogue/DRSMG
|
||||
dialyses
|
||||
dialysis/M
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
<!ENTITY formatD "%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 -->
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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": []
|
||||
}]
|
||||
}
|
|
@ -221,7 +221,7 @@ this.FxAccountsManager = {
|
|||
}
|
||||
);
|
||||
}
|
||||
return Promise.reject(reason);
|
||||
return Promise.reject(reason.message ? { error: reason.message } : reason);
|
||||
},
|
||||
|
||||
_getAssertion: function(aAudience, aPrincipal) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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 + ")");
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
Загрузка…
Ссылка в новой задаче