зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c. a=merge
This commit is contained in:
Коммит
7fbee60fc9
|
@ -600,7 +600,7 @@ SocialShare = {
|
|||
sizeSocialPanelToContent(this.panel, iframe);
|
||||
},
|
||||
|
||||
sharePage: function(providerOrigin, graphData) {
|
||||
sharePage: function(providerOrigin, graphData, target) {
|
||||
// if providerOrigin is undefined, we use the last-used provider, or the
|
||||
// current/default provider. The provider selection in the share panel
|
||||
// will call sharePage with an origin for us to switch to.
|
||||
|
@ -619,7 +619,8 @@ SocialShare = {
|
|||
// in mozSocial API, or via nsContentMenu calls. If it is present, it MUST
|
||||
// define at least url. If it is undefined, we're sharing the current url in
|
||||
// the browser tab.
|
||||
let sharedURI = graphData ? Services.io.newURI(graphData.url, null, null) :
|
||||
let pageData = graphData ? graphData : this.currentShare;
|
||||
let sharedURI = pageData ? Services.io.newURI(pageData.url, null, null) :
|
||||
gBrowser.currentURI;
|
||||
if (!this.canSharePage(sharedURI))
|
||||
return;
|
||||
|
@ -628,7 +629,6 @@ SocialShare = {
|
|||
// endpoints (e.g. oexchange) that do not support additional
|
||||
// socialapi functionality. One tweak is that we shoot an event
|
||||
// containing the open graph data.
|
||||
let pageData = graphData ? graphData : this.currentShare;
|
||||
if (!pageData || sharedURI == gBrowser.currentURI) {
|
||||
pageData = OpenGraphBuilder.getData(gBrowser);
|
||||
if (graphData) {
|
||||
|
@ -638,6 +638,10 @@ SocialShare = {
|
|||
}
|
||||
}
|
||||
}
|
||||
// if this is a share of a selected item, get any microdata
|
||||
if (!pageData.microdata && target) {
|
||||
pageData.microdata = OpenGraphBuilder.getMicrodata(gBrowser, target);
|
||||
}
|
||||
this.currentShare = pageData;
|
||||
|
||||
let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, pageData);
|
||||
|
@ -1353,10 +1357,10 @@ SocialMarks = {
|
|||
return this._toolbarHelper;
|
||||
},
|
||||
|
||||
markLink: function(aOrigin, aUrl) {
|
||||
markLink: function(aOrigin, aUrl, aTarget) {
|
||||
// find the button for this provider, and open it
|
||||
let id = this._toolbarHelper.idFromOrigin(aOrigin);
|
||||
document.getElementById(id).markLink(aUrl);
|
||||
document.getElementById(id).markLink(aUrl, aTarget);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1913,6 +1913,7 @@ function loadURI(uri, referrer, postData, allowThirdPartyFixup) {
|
|||
}
|
||||
|
||||
function getShortcutOrURIAndPostData(aURL, aCallback) {
|
||||
let mayInheritPrincipal = false;
|
||||
let postData = null;
|
||||
let shortcutURL = null;
|
||||
let keyword = aURL;
|
||||
|
@ -1928,7 +1929,8 @@ function getShortcutOrURIAndPostData(aURL, aCallback) {
|
|||
if (engine) {
|
||||
let submission = engine.getSubmission(param);
|
||||
postData = submission.postData;
|
||||
aCallback({ postData: submission.postData, url: submission.uri.spec });
|
||||
aCallback({ postData: submission.postData, url: submission.uri.spec,
|
||||
mayInheritPrincipal: mayInheritPrincipal });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1936,7 +1938,8 @@ function getShortcutOrURIAndPostData(aURL, aCallback) {
|
|||
PlacesUtils.getURLAndPostDataForKeyword(keyword);
|
||||
|
||||
if (!shortcutURL) {
|
||||
aCallback({ postData: postData, url: aURL });
|
||||
aCallback({ postData: postData, url: aURL,
|
||||
mayInheritPrincipal: mayInheritPrincipal });
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1968,7 +1971,12 @@ function getShortcutOrURIAndPostData(aURL, aCallback) {
|
|||
postData = getPostDataStream(escapedPostData, param, encodedParam,
|
||||
"application/x-www-form-urlencoded");
|
||||
|
||||
aCallback({ postData: postData, url: shortcutURL });
|
||||
// This URL came from a bookmark, so it's safe to let it inherit the current
|
||||
// document's principal.
|
||||
mayInheritPrincipal = true;
|
||||
|
||||
aCallback({ postData: postData, url: shortcutURL,
|
||||
mayInheritPrincipal: mayInheritPrincipal });
|
||||
}
|
||||
|
||||
if (matches) {
|
||||
|
@ -1991,9 +1999,15 @@ function getShortcutOrURIAndPostData(aURL, aCallback) {
|
|||
// the original URL.
|
||||
postData = null;
|
||||
|
||||
aCallback({ postData: postData, url: aURL });
|
||||
aCallback({ postData: postData, url: aURL,
|
||||
mayInheritPrincipal: mayInheritPrincipal });
|
||||
} else {
|
||||
aCallback({ postData: postData, url: shortcutURL });
|
||||
// This URL came from a bookmark, so it's safe to let it inherit the current
|
||||
// document's principal.
|
||||
mayInheritPrincipal = true;
|
||||
|
||||
aCallback({ postData: postData, url: shortcutURL,
|
||||
mayInheritPrincipal: mayInheritPrincipal });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2842,7 +2856,7 @@ const DOMLinkHandler = {
|
|||
init: function() {
|
||||
let mm = window.messageManager;
|
||||
mm.addMessageListener("Link:AddFeed", this);
|
||||
mm.addMessageListener("Link:AddIcon", this);
|
||||
mm.addMessageListener("Link:SetIcon", this);
|
||||
mm.addMessageListener("Link:AddSearch", this);
|
||||
},
|
||||
|
||||
|
@ -2853,8 +2867,8 @@ const DOMLinkHandler = {
|
|||
FeedHandler.addFeed(link, aMsg.target);
|
||||
break;
|
||||
|
||||
case "Link:AddIcon":
|
||||
return this.addIcon(aMsg.target, aMsg.data.url);
|
||||
case "Link:SetIcon":
|
||||
return this.setIcon(aMsg.target, aMsg.data.url);
|
||||
break;
|
||||
|
||||
case "Link:AddSearch":
|
||||
|
@ -2863,7 +2877,7 @@ const DOMLinkHandler = {
|
|||
}
|
||||
},
|
||||
|
||||
addIcon: function(aBrowser, aURL) {
|
||||
setIcon: function(aBrowser, aURL) {
|
||||
if (gBrowser.isFailedIcon(aURL))
|
||||
return false;
|
||||
|
||||
|
@ -5181,7 +5195,8 @@ function middleMousePaste(event) {
|
|||
if (where != "current" ||
|
||||
lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
|
||||
openUILink(data.url, event,
|
||||
{ ignoreButton: true });
|
||||
{ ignoreButton: true,
|
||||
disallowInheritPrincipal: !data.mayInheritPrincipal });
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -5189,26 +5204,9 @@ function middleMousePaste(event) {
|
|||
}
|
||||
|
||||
function stripUnsafeProtocolOnPaste(pasteData) {
|
||||
// Don't allow pasting in full URIs which inherit the security context.
|
||||
const URI_INHERITS_SECURITY_CONTEXT = Ci.nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT;
|
||||
|
||||
let pastedURI;
|
||||
try {
|
||||
pastedURI = makeURI(pasteData.trim());
|
||||
} catch (ex) {
|
||||
return pasteData;
|
||||
}
|
||||
|
||||
while (Services.netutil.URIChainHasFlags(pastedURI, URI_INHERITS_SECURITY_CONTEXT)) {
|
||||
pasteData = pastedURI.path.trim();
|
||||
try {
|
||||
pastedURI = makeURI(pasteData);
|
||||
} catch (ex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return pasteData;
|
||||
// Don't allow pasting javascript URIs since we don't support
|
||||
// LOAD_FLAGS_DISALLOW_INHERIT_OWNER for those.
|
||||
return pasteData.replace(/^(?:\s*javascript:)+/i, "");
|
||||
}
|
||||
|
||||
function handleDroppedLink(event, url, name)
|
||||
|
|
|
@ -1591,22 +1591,22 @@ nsContextMenu.prototype = {
|
|||
},
|
||||
markLink: function CM_markLink(origin) {
|
||||
// send link to social, if it is the page url linkURI will be null
|
||||
SocialMarks.markLink(origin, this.linkURI ? this.linkURI.spec : null);
|
||||
SocialMarks.markLink(origin, this.linkURI ? this.linkURI.spec : null, this.target);
|
||||
},
|
||||
shareLink: function CM_shareLink() {
|
||||
SocialShare.sharePage(null, { url: this.linkURI.spec });
|
||||
SocialShare.sharePage(null, { url: this.linkURI.spec }, this.target);
|
||||
},
|
||||
|
||||
shareImage: function CM_shareImage() {
|
||||
SocialShare.sharePage(null, { url: this.imageURL, previews: [ this.mediaURL ] });
|
||||
SocialShare.sharePage(null, { url: this.imageURL, previews: [ this.mediaURL ] }, this.target);
|
||||
},
|
||||
|
||||
shareVideo: function CM_shareVideo() {
|
||||
SocialShare.sharePage(null, { url: this.mediaURL, source: this.mediaURL });
|
||||
SocialShare.sharePage(null, { url: this.mediaURL, source: this.mediaURL }, this.target);
|
||||
},
|
||||
|
||||
shareSelect: function CM_shareSelect(selection) {
|
||||
SocialShare.sharePage(null, { url: this.browser.currentURI.spec, text: selection });
|
||||
SocialShare.sharePage(null, { url: this.browser.currentURI.spec, text: selection }, this.target);
|
||||
},
|
||||
|
||||
savePageAs: function CM_savePageAs() {
|
||||
|
|
|
@ -147,6 +147,7 @@
|
|||
|
||||
<method name="loadPanel">
|
||||
<parameter name="pageData"/>
|
||||
<parameter name="target"/>
|
||||
<body><![CDATA[
|
||||
let provider = this.provider;
|
||||
let panel = this.panel;
|
||||
|
@ -157,7 +158,13 @@
|
|||
panel.appendChild(this.content);
|
||||
|
||||
let URLTemplate = provider.markURL;
|
||||
this.pageData = pageData || OpenGraphBuilder.getData(gBrowser);
|
||||
pageData = pageData || OpenGraphBuilder.getData(gBrowser);
|
||||
// if this is a share of a selected item, get any microdata
|
||||
if (!pageData.microdata && target) {
|
||||
pageData.microdata = OpenGraphBuilder.getMicrodata(gBrowser, target);
|
||||
}
|
||||
this.pageData = pageData;
|
||||
|
||||
let endpoint = OpenGraphBuilder.generateEndpointURL(URLTemplate, this.pageData);
|
||||
|
||||
// setup listeners
|
||||
|
@ -248,6 +255,7 @@
|
|||
|
||||
<method name="markLink">
|
||||
<parameter name="aUrl"/>
|
||||
<parameter name="aTarget"/>
|
||||
<body><![CDATA[
|
||||
if (!aUrl) {
|
||||
this.markCurrentPage(true);
|
||||
|
@ -259,7 +267,7 @@
|
|||
// link, etc. inside the page. We also "update" the iframe to the
|
||||
// previous url when it is closed.
|
||||
this.content.setAttribute("src", "about:blank");
|
||||
this.loadPanel({ url: aUrl });
|
||||
this.loadPanel({ url: aUrl }, aTarget);
|
||||
this.openPanel(true);
|
||||
]]></body>
|
||||
</method>
|
||||
|
|
|
@ -53,6 +53,7 @@ support-files =
|
|||
file_bug970276_favicon1.ico
|
||||
file_bug970276_favicon2.ico
|
||||
file_dom_notifications.html
|
||||
file_favicon_change.html
|
||||
file_fullscreen-window-open.html
|
||||
get_user_media.html
|
||||
head.js
|
||||
|
@ -290,6 +291,7 @@ skip-if = e10s # Bug 918663 - DOMLinkAdded events don't make their way to chrome
|
|||
[browser_duplicateIDs.js]
|
||||
[browser_drag.js]
|
||||
skip-if = true # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
|
||||
[browser_favicon_change.js]
|
||||
[browser_findbarClose.js]
|
||||
skip-if = e10s # Bug ?????? - test directly manipulates content (tries to grab an iframe directly from content)
|
||||
[browser_fullscreen-window-open.js]
|
||||
|
@ -311,6 +313,7 @@ skip-if = e10s # Bug ?????? - test directly manipulates content (gBrowser.conten
|
|||
skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405)
|
||||
[browser_locationBarCommand.js]
|
||||
skip-if = os == "linux" || e10s # Linux: Intermittent failures, bug 917535; e10s: Bug ?????? - Focus issues (There should be no focused element - Got [object XULElement], expected null)
|
||||
[browser_locationBarExternalLoad.js]
|
||||
[browser_menuButtonFitts.js]
|
||||
skip-if = os != "win" || e10s # The Fitts Law menu button is only supported on Windows (bug 969376); # Bug ?????? - URL bar issues ("There should be no focused element - Got [object XULElement], expected null")
|
||||
[browser_middleMouse_noJSPaste.js]
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URL = "http://mochi.test:8888/browser/browser/base/content/test/general/file_favicon_change.html"
|
||||
|
||||
add_task(function*() {
|
||||
let extraTab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
let tabLoaded = promiseTabLoaded(extraTab);
|
||||
extraTab.linkedBrowser.loadURI(TEST_URL);
|
||||
let expectedFavicon = "http://example.org/one-icon";
|
||||
let haveChanged = new Promise.defer();
|
||||
let observer = new MutationObserver(function(mutations) {
|
||||
for (let mut of mutations) {
|
||||
if (mut.attributeName != "image") {
|
||||
continue;
|
||||
}
|
||||
let imageVal = extraTab.getAttribute("image").replace(/#.*$/, "");
|
||||
if (!imageVal) {
|
||||
// The value gets removed because it doesn't load.
|
||||
continue;
|
||||
}
|
||||
is(imageVal, expectedFavicon, "Favicon image should correspond to expected image.");
|
||||
haveChanged.resolve();
|
||||
}
|
||||
});
|
||||
observer.observe(extraTab, {attributes: true});
|
||||
yield tabLoaded;
|
||||
yield haveChanged.promise;
|
||||
haveChanged = new Promise.defer();
|
||||
expectedFavicon = "http://example.org/other-icon";
|
||||
let contentWin = extraTab.linkedBrowser.contentWindow;
|
||||
let ev = new contentWin.CustomEvent("PleaseChangeFavicon", {});
|
||||
contentWin.dispatchEvent(ev);
|
||||
yield haveChanged.promise;
|
||||
observer.disconnect();
|
||||
gBrowser.removeTab(extraTab);
|
||||
});
|
||||
|
|
@ -11,9 +11,10 @@ function getPostDataString(aIS) {
|
|||
return dataLines[dataLines.length-1];
|
||||
}
|
||||
|
||||
function keywordResult(aURL, aPostData) {
|
||||
function keywordResult(aURL, aPostData, aIsUnsafe) {
|
||||
this.url = aURL;
|
||||
this.postData = aPostData;
|
||||
this.isUnsafe = aIsUnsafe;
|
||||
}
|
||||
|
||||
function keyWordData() {}
|
||||
|
@ -52,20 +53,20 @@ var testData = [
|
|||
new keywordResult("http://bmget-nosearch/", null)],
|
||||
|
||||
[new searchKeywordData("searchget", "http://searchget/?search={searchTerms}", null, "foo4"),
|
||||
new keywordResult("http://searchget/?search=foo4", null)],
|
||||
new keywordResult("http://searchget/?search=foo4", null, true)],
|
||||
|
||||
[new searchKeywordData("searchpost", "http://searchpost/", "search={searchTerms}", "foo5"),
|
||||
new keywordResult("http://searchpost/", "search=foo5")],
|
||||
new keywordResult("http://searchpost/", "search=foo5", true)],
|
||||
|
||||
[new searchKeywordData("searchpostget", "http://searchpostget/?search1={searchTerms}", "search2={searchTerms}", "foo6"),
|
||||
new keywordResult("http://searchpostget/?search1=foo6", "search2=foo6")],
|
||||
new keywordResult("http://searchpostget/?search1=foo6", "search2=foo6", true)],
|
||||
|
||||
// Bookmark keywords that don't take parameters should not be activated if a
|
||||
// parameter is passed (bug 420328).
|
||||
[new bmKeywordData("bmget-noparam", "http://bmget-noparam/", null, "foo7"),
|
||||
new keywordResult(null, null)],
|
||||
new keywordResult(null, null, true)],
|
||||
[new bmKeywordData("bmpost-noparam", "http://bmpost-noparam/", "not_a=param", "foo8"),
|
||||
new keywordResult(null, null)],
|
||||
new keywordResult(null, null, true)],
|
||||
|
||||
// Test escaping (%s = escaped, %S = raw)
|
||||
// UTF-8 default
|
||||
|
@ -87,7 +88,7 @@ var testData = [
|
|||
// getShortcutOrURIAndPostData for non-keywords (setupKeywords only adds keywords for
|
||||
// bmKeywordData objects)
|
||||
[{keyword: "http://gavinsharp.com"},
|
||||
new keywordResult(null, null)]
|
||||
new keywordResult(null, null, true)]
|
||||
];
|
||||
|
||||
function test() {
|
||||
|
@ -108,6 +109,7 @@ function test() {
|
|||
let expected = result.url || query;
|
||||
is(returnedData.url, expected, "got correct URL for " + data.keyword);
|
||||
is(getPostDataString(returnedData.postData), result.postData, "got correct postData for " + data.keyword);
|
||||
is(returnedData.mayInheritPrincipal, !result.isUnsafe, "got correct mayInheritPrincipal for " + data.keyword);
|
||||
}
|
||||
cleanupKeywords();
|
||||
}).then(finish);
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
nextTest();
|
||||
}
|
||||
|
||||
let urls = [
|
||||
"data:text/html,<body>hi"
|
||||
];
|
||||
|
||||
function urlEnter(url) {
|
||||
gURLBar.value = url;
|
||||
gURLBar.focus();
|
||||
EventUtils.synthesizeKey("VK_RETURN", {});
|
||||
}
|
||||
|
||||
function urlClick(url) {
|
||||
gURLBar.value = url;
|
||||
gURLBar.focus();
|
||||
let goButton = document.getElementById("urlbar-go-button");
|
||||
EventUtils.synthesizeMouseAtCenter(goButton, {});
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
let url = urls.shift();
|
||||
if (url) {
|
||||
testURL(url, urlEnter, function () {
|
||||
testURL(url, urlClick, nextTest);
|
||||
});
|
||||
}
|
||||
else
|
||||
finish();
|
||||
}
|
||||
|
||||
function testURL(url, loadFunc, endFunc) {
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
registerCleanupFunction(function () {
|
||||
gBrowser.removeTab(tab);
|
||||
});
|
||||
addPageShowListener(function () {
|
||||
let pagePrincipal = gBrowser.contentPrincipal;
|
||||
loadFunc(url);
|
||||
|
||||
addPageShowListener(function () {
|
||||
let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
|
||||
is(fm.focusedElement, null, "should be no focused element");
|
||||
is(fm.focusedWindow, gBrowser.contentWindow, "content window should be focused");
|
||||
|
||||
ok(!gBrowser.contentPrincipal.equals(pagePrincipal),
|
||||
"load of " + url + " by " + loadFunc.name + " should produce a page with a different principal");
|
||||
endFunc();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addPageShowListener(func) {
|
||||
gBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() {
|
||||
gBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false);
|
||||
func();
|
||||
});
|
||||
}
|
|
@ -7,13 +7,13 @@ let pairs = [
|
|||
["javascript:", ""],
|
||||
["javascript:1+1", "1+1"],
|
||||
["javascript:document.domain", "document.domain"],
|
||||
["data:text/html,<body>hi</body>", "text/html,<body>hi</body>"],
|
||||
["data:text/html,<body>hi</body>", "data:text/html,<body>hi</body>"],
|
||||
// Nested things get confusing because some things don't parse as URIs:
|
||||
["javascript:javascript:alert('hi!')", "alert('hi!')"],
|
||||
["data:data:text/html,<body>hi</body>", "text/html,<body>hi</body>"],
|
||||
["data:data:text/html,<body>hi</body>", "data:data:text/html,<body>hi</body>"],
|
||||
["javascript:data:javascript:alert('hi!')", "data:javascript:alert('hi!')"],
|
||||
["javascript:data:text/html,javascript:alert('hi!')", "text/html,javascript:alert('hi!')"],
|
||||
["data:data:text/html,javascript:alert('hi!')", "text/html,javascript:alert('hi!')"],
|
||||
["javascript:data:text/html,javascript:alert('hi!')", "data:text/html,javascript:alert('hi!')"],
|
||||
["data:data:text/html,javascript:alert('hi!')", "data:data:text/html,javascript:alert('hi!')"],
|
||||
];
|
||||
|
||||
let clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<link rel="icon" href="http://example.org/one-icon" type="image/ico" id="i">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.addEventListener("PleaseChangeFavicon", function() {
|
||||
var ico = document.getElementById("i");
|
||||
ico.setAttribute("href", "http://example.org/other-icon");
|
||||
});
|
||||
</script>
|
||||
</body></html>
|
|
@ -9,6 +9,7 @@ support-files =
|
|||
opengraph/shortlink_linkrel.html
|
||||
opengraph/shorturl_link.html
|
||||
opengraph/shorturl_linkrel.html
|
||||
microdata.html
|
||||
share.html
|
||||
social_activate.html
|
||||
social_activate_iframe.html
|
||||
|
|
|
@ -170,5 +170,76 @@ var tests = {
|
|||
}
|
||||
port.postMessage({topic: "test-init"});
|
||||
executeSoon(runOneTest);
|
||||
},
|
||||
testShareMicrodata: function(next) {
|
||||
SocialService.addProvider(manifest, function(provider) {
|
||||
let port = provider.getWorkerPort();
|
||||
let target, testTab;
|
||||
|
||||
let expecting = JSON.stringify({
|
||||
"url": "https://example.com/browser/browser/base/content/test/social/microdata.html",
|
||||
"title": "My Blog",
|
||||
"previews": [],
|
||||
"microdata": {
|
||||
"items": [{
|
||||
"types": ["http://schema.org/BlogPosting"],
|
||||
"properties": {
|
||||
"headline": ["Progress report"],
|
||||
"datePublished": ["2013-08-29"],
|
||||
"url": ["https://example.com/browser/browser/base/content/test/social/microdata.html?comments=0"],
|
||||
"comment": [{
|
||||
"types": ["http://schema.org/UserComments"],
|
||||
"properties": {
|
||||
"url": ["https://example.com/browser/browser/base/content/test/social/microdata.html#c1"],
|
||||
"creator": [{
|
||||
"types": ["http://schema.org/Person"],
|
||||
"properties": {
|
||||
"name": ["Greg"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"commentTime": ["2013-08-29"]
|
||||
}
|
||||
}, {
|
||||
"types": ["http://schema.org/UserComments"],
|
||||
"properties": {
|
||||
"url": ["https://example.com/browser/browser/base/content/test/social/microdata.html#c2"],
|
||||
"creator": [{
|
||||
"types": ["http://schema.org/Person"],
|
||||
"properties": {
|
||||
"name": ["Charlotte"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"commentTime": ["2013-08-29"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
port.onmessage = function (e) {
|
||||
let topic = e.data.topic;
|
||||
switch (topic) {
|
||||
case "got-share-data-message":
|
||||
is(JSON.stringify(e.data.result), expecting, "microdata data ok");
|
||||
gBrowser.removeTab(testTab);
|
||||
SocialService.removeProvider(manifest.origin, next);
|
||||
break;
|
||||
}
|
||||
}
|
||||
port.postMessage({topic: "test-init"});
|
||||
|
||||
let url = "https://example.com/browser/browser/base/content/test/social/microdata.html"
|
||||
addTab(url, function(tab) {
|
||||
testTab = tab;
|
||||
let doc = tab.linkedBrowser.contentDocument;
|
||||
target = doc.getElementById("simple-hcard");
|
||||
SocialShare.sharePage(manifest.origin, null, target);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -280,6 +280,56 @@ var tests = {
|
|||
});
|
||||
},
|
||||
|
||||
testMarkMicrodata: function(next) {
|
||||
let provider = Social._getProviderFromOrigin(manifest2.origin);
|
||||
let port = provider.getWorkerPort();
|
||||
let target, testTab;
|
||||
|
||||
// browser_share tests microdata on the full page, this is testing a
|
||||
// specific target element.
|
||||
let expecting = JSON.stringify({
|
||||
"url": "https://example.com/browser/browser/base/content/test/social/microdata.html",
|
||||
"microdata": {
|
||||
"items": [{
|
||||
"types": ["http://schema.org/UserComments"],
|
||||
"properties": {
|
||||
"url": ["https://example.com/browser/browser/base/content/test/social/microdata.html#c2"],
|
||||
"creator": [{
|
||||
"types": ["http://schema.org/Person"],
|
||||
"properties": {
|
||||
"name": ["Charlotte"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"commentTime": ["2013-08-29"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
port.onmessage = function (e) {
|
||||
let topic = e.data.topic;
|
||||
switch (topic) {
|
||||
case "got-share-data-message":
|
||||
is(JSON.stringify(e.data.result), expecting, "microdata data ok");
|
||||
gBrowser.removeTab(testTab);
|
||||
port.close();
|
||||
next();
|
||||
break;
|
||||
}
|
||||
}
|
||||
port.postMessage({topic: "test-init"});
|
||||
|
||||
let url = "https://example.com/browser/browser/base/content/test/social/microdata.html"
|
||||
addTab(url, function(tab) {
|
||||
testTab = tab;
|
||||
let doc = tab.linkedBrowser.contentDocument;
|
||||
target = doc.getElementById("test-comment");
|
||||
SocialMarks.markLink(manifest2.origin, url, target);
|
||||
});
|
||||
},
|
||||
|
||||
testButtonOnDisable: function(next) {
|
||||
// enable the provider now
|
||||
let provider = Social._getProviderFromOrigin(manifest2.origin);
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE HTML>
|
||||
<head>
|
||||
<title>My Blog</title>
|
||||
</head>
|
||||
<body>
|
||||
<article itemscope itemtype="http://schema.org/BlogPosting">
|
||||
<header>
|
||||
<h1 itemprop="headline">Progress report</h1>
|
||||
<p><time itemprop="datePublished" datetime="2013-08-29">today</time></p>
|
||||
<link itemprop="url" href="?comments=0">
|
||||
</header>
|
||||
<p>All in all, he's doing well with his swim lessons. The biggest thing was he had trouble
|
||||
putting his head in, but we got it down.</p>
|
||||
<section>
|
||||
<h1>Comments</h1>
|
||||
<article itemprop="comment" itemscope itemtype="http://schema.org/UserComments" id="c1">
|
||||
<link itemprop="url" href="#c1">
|
||||
<footer>
|
||||
<p>Posted by: <span itemprop="creator" itemscope itemtype="http://schema.org/Person">
|
||||
<span itemprop="name">Greg</span>
|
||||
</span></p>
|
||||
<p><time itemprop="commentTime" datetime="2013-08-29">15 minutes ago</time></p>
|
||||
</footer>
|
||||
<p>Ha!</p>
|
||||
</article>
|
||||
<article id="test-comment" itemprop="comment" itemscope itemtype="http://schema.org/UserComments" id="c2">
|
||||
<link itemprop="url" href="#c2">
|
||||
<footer>
|
||||
<p>Posted by: <span itemprop="creator" itemscope itemtype="http://schema.org/Person">
|
||||
<span itemprop="name">Charlotte</span>
|
||||
</span></p>
|
||||
<p><time itemprop="commentTime" datetime="2013-08-29">5 minutes ago</time></p>
|
||||
</footer>
|
||||
<p>When you say "we got it down"...</p>
|
||||
</article>
|
||||
</section>
|
||||
</article>
|
||||
</body>
|
|
@ -34,6 +34,8 @@
|
|||
shareData = JSON.parse(e.detail);
|
||||
updateTextNode(document.getElementById("shared"), shareData.url);
|
||||
socialMarkUpdate(true);
|
||||
var port = navigator.mozSocial.getWorker().port;
|
||||
port.postMessage({topic: "share-data-message", result: shareData});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
@ -265,6 +265,7 @@
|
|||
return; // Do nothing for right clicks
|
||||
|
||||
var url = this.value;
|
||||
var mayInheritPrincipal = false;
|
||||
var postData = null;
|
||||
|
||||
var action = this._parseActionUrl(url);
|
||||
|
@ -287,7 +288,7 @@
|
|||
}
|
||||
else {
|
||||
this._canonizeURL(aTriggeringEvent, response => {
|
||||
[url, postData] = response;
|
||||
[url, postData, mayInheritPrincipal] = response;
|
||||
if (url) {
|
||||
matchLastLocationChange = (lastLocationChange ==
|
||||
gBrowser.selectedBrowser.lastLocationChange);
|
||||
|
@ -309,10 +310,11 @@
|
|||
}
|
||||
|
||||
function loadCurrent() {
|
||||
let webnav = Ci.nsIWebNavigation;
|
||||
let flags = webnav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
|
||||
webnav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
|
||||
gBrowser.loadURIWithFlags(url, flags, null, null, postData);
|
||||
openUILinkIn(url, "current", {
|
||||
allowThirdPartyFixup: true,
|
||||
disallowInheritPrincipal: !mayInheritPrincipal,
|
||||
postData: postData
|
||||
});
|
||||
}
|
||||
|
||||
// Focus the content area before triggering loads, since if the load
|
||||
|
@ -422,7 +424,7 @@
|
|||
}
|
||||
|
||||
getShortcutOrURIAndPostData(url, data => {
|
||||
aCallback([data.url, data.postData]);
|
||||
aCallback([data.url, data.postData, data.mayInheritPrincipal]);
|
||||
});
|
||||
]]></body>
|
||||
</method>
|
||||
|
|
|
@ -281,11 +281,18 @@ function openLinkIn(url, where, params) {
|
|||
getBoolPref("browser.tabs.loadInBackground");
|
||||
}
|
||||
|
||||
let uriObj;
|
||||
if (where == "current") {
|
||||
try {
|
||||
uriObj = Services.io.newURI(url, null, null);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (where == "current" && w.gBrowser.selectedTab.pinned) {
|
||||
try {
|
||||
let uriObj = Services.io.newURI(url, null, null);
|
||||
if (!uriObj.schemeIs("javascript") &&
|
||||
w.gBrowser.currentURI.host != uriObj.host) {
|
||||
// nsIURI.host can throw for non-nsStandardURL nsIURIs.
|
||||
if (!uriObj || (!uriObj.schemeIs("javascript") &&
|
||||
w.gBrowser.currentURI.host != uriObj.host)) {
|
||||
where = "tab";
|
||||
loadInBackground = false;
|
||||
}
|
||||
|
@ -302,12 +309,19 @@ function openLinkIn(url, where, params) {
|
|||
switch (where) {
|
||||
case "current":
|
||||
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
|
||||
|
||||
if (aAllowThirdPartyFixup) {
|
||||
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
|
||||
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
|
||||
}
|
||||
if (aDisallowInheritPrincipal)
|
||||
|
||||
// LOAD_FLAGS_DISALLOW_INHERIT_OWNER isn't supported for javascript URIs,
|
||||
// i.e. it causes them not to load at all. Callers should strip
|
||||
// "javascript:" from pasted strings to protect users from malicious URIs
|
||||
// (see stripUnsafeProtocolOnPaste).
|
||||
if (aDisallowInheritPrincipal && !(uriObj && uriObj.schemeIs("javascript")))
|
||||
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
|
||||
|
||||
w.gBrowser.loadURIWithFlags(url, flags, aReferrerURI, null, aPostData);
|
||||
break;
|
||||
case "tabshifted":
|
||||
|
|
|
@ -467,7 +467,6 @@ var BookmarkPropertiesPanel = {
|
|||
// The order here is important! We have to uninit the panel first, otherwise
|
||||
// late changes could force it to commit more transactions.
|
||||
gEditItemOverlay.uninitPanel(true);
|
||||
gEditItemOverlay = null;
|
||||
this._endBatch();
|
||||
window.arguments[0].performed = true;
|
||||
},
|
||||
|
@ -477,7 +476,6 @@ var BookmarkPropertiesPanel = {
|
|||
// changes done as part of Undo may change the panel contents and by
|
||||
// that force it to commit more transactions.
|
||||
gEditItemOverlay.uninitPanel(true);
|
||||
gEditItemOverlay = null;
|
||||
this._endBatch();
|
||||
PlacesUtils.transactionManager.undoTransaction();
|
||||
window.arguments[0].performed = false;
|
||||
|
|
|
@ -209,6 +209,12 @@ var gEditItemOverlay = {
|
|||
// observe only tags changes, through bookmarks.
|
||||
if (this._itemId != -1 || this._uri || this._multiEdit)
|
||||
PlacesUtils.bookmarks.addObserver(this, false);
|
||||
|
||||
this._element("namePicker").addEventListener("blur", this);
|
||||
this._element("locationField").addEventListener("blur", this);
|
||||
this._element("tagsField").addEventListener("blur", this);
|
||||
this._element("keywordField").addEventListener("blur", this);
|
||||
this._element("descriptionField").addEventListener("blur", this);
|
||||
window.addEventListener("unload", this, false);
|
||||
this._observersAdded = true;
|
||||
}
|
||||
|
@ -388,6 +394,12 @@ var gEditItemOverlay = {
|
|||
if (this._itemId != -1 || this._uri || this._multiEdit)
|
||||
PlacesUtils.bookmarks.removeObserver(this);
|
||||
|
||||
this._element("namePicker").removeEventListener("blur", this);
|
||||
this._element("locationField").removeEventListener("blur", this);
|
||||
this._element("tagsField").removeEventListener("blur", this);
|
||||
this._element("keywordField").removeEventListener("blur", this);
|
||||
this._element("descriptionField").removeEventListener("blur", this);
|
||||
|
||||
this._observersAdded = false;
|
||||
}
|
||||
|
||||
|
@ -528,7 +540,7 @@ var gEditItemOverlay = {
|
|||
return false;
|
||||
},
|
||||
|
||||
onNamePickerChange: function EIO_onNamePickerChange() {
|
||||
onNamePickerBlur: function EIO_onNamePickerBlur() {
|
||||
if (this._itemId == -1)
|
||||
return;
|
||||
|
||||
|
@ -879,6 +891,11 @@ var gEditItemOverlay = {
|
|||
this._element("tagsField").value = tags.join(", ");
|
||||
this._updateTags();
|
||||
break;
|
||||
case "blur":
|
||||
let replaceFn = (str, firstLetter) => firstLetter.toUpperCase();
|
||||
let nodeName = aEvent.target.id.replace(/editBMPanel_(\w)/, replaceFn);
|
||||
this["on" + nodeName + "Blur"]();
|
||||
break;
|
||||
case "unload":
|
||||
this.uninitPanel(false);
|
||||
break;
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
control="editBMPanel_namePicker"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
<textbox id="editBMPanel_namePicker"
|
||||
onblur="gEditItemOverlay.onNamePickerChange();"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
</row>
|
||||
|
||||
|
@ -45,7 +44,6 @@
|
|||
observes="paneElementsBroadcaster"/>
|
||||
<textbox id="editBMPanel_locationField"
|
||||
class="uri-element"
|
||||
onblur="gEditItemOverlay.onLocationFieldBlur();"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
</row>
|
||||
|
||||
|
@ -150,7 +148,6 @@
|
|||
completedefaultindex="true"
|
||||
tabscrolling="true"
|
||||
showcommentcolumn="true"
|
||||
onblur="gEditItemOverlay.onTagsFieldBlur();"
|
||||
observes="paneElementsBroadcaster"
|
||||
placeholder="&editBookmarkOverlay.tagsEmptyDesc.label;"/>
|
||||
<button id="editBMPanel_tagsSelectorExpander"
|
||||
|
@ -180,7 +177,6 @@
|
|||
control="editBMPanel_keywordField"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
<textbox id="editBMPanel_keywordField"
|
||||
onblur="gEditItemOverlay.onKeywordFieldBlur();"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
</row>
|
||||
|
||||
|
@ -193,7 +189,6 @@
|
|||
observes="paneElementsBroadcaster"/>
|
||||
<textbox id="editBMPanel_descriptionField"
|
||||
multiline="true"
|
||||
onblur="gEditItemOverlay.onDescriptionFieldBlur();"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
</row>
|
||||
</rows>
|
||||
|
|
|
@ -127,7 +127,7 @@ gTests.push({
|
|||
PlacesUtils.bookmarks.getItemTitle(PlacesUtils.unfiledBookmarksFolderId),
|
||||
"Node title is correct");
|
||||
// Blur the field and ensure root's name has not been changed.
|
||||
this.window.gEditItemOverlay.onNamePickerChange();
|
||||
this.window.gEditItemOverlay.onNamePickerBlur();
|
||||
is(namepicker.value,
|
||||
PlacesUtils.bookmarks.getItemTitle(PlacesUtils.unfiledBookmarksFolderId),
|
||||
"Root title is correct");
|
||||
|
|
|
@ -1693,10 +1693,8 @@ var gApplicationsPane = {
|
|||
var typeItem = this._list.selectedItem;
|
||||
var handlerInfo = this._handledTypes[typeItem.type];
|
||||
|
||||
openDialog("chrome://browser/content/preferences/applicationManager.xul",
|
||||
"",
|
||||
"modal,centerscreen,resizable=no",
|
||||
handlerInfo);
|
||||
gSubDialog.open("chrome://browser/content/preferences/applicationManager.xul",
|
||||
"resizable=no", handlerInfo);
|
||||
|
||||
// Rebuild the actions menu so that we revert to the previous selection,
|
||||
// or "Always ask" if the previous default application has been removed
|
||||
|
@ -1757,9 +1755,8 @@ var gApplicationsPane = {
|
|||
params.filename = null;
|
||||
params.handlerApp = null;
|
||||
|
||||
window.openDialog("chrome://global/content/appPicker.xul", null,
|
||||
"chrome,modal,centerscreen,titlebar,dialog=yes",
|
||||
params);
|
||||
gSubDialog.open("chrome://global/content/appPicker.xul",
|
||||
null, params);
|
||||
|
||||
if (this.isValidHandlerApp(params.handlerApp)) {
|
||||
handlerApp = params.handlerApp;
|
||||
|
|
|
@ -222,12 +222,19 @@ TranslationUI.prototype = {
|
|||
// Check if we should never show the infobar for this language.
|
||||
let neverForLangs =
|
||||
Services.prefs.getCharPref("browser.translation.neverForLanguages");
|
||||
if (neverForLangs.split(",").indexOf(this.detectedLanguage) != -1)
|
||||
if (neverForLangs.split(",").indexOf(this.detectedLanguage) != -1) {
|
||||
TranslationHealthReport.recordAutoRejectedTranslationOffer();
|
||||
return false;
|
||||
}
|
||||
|
||||
// or if we should never show the infobar for this domain.
|
||||
let perms = Services.perms;
|
||||
return perms.testExactPermission(aURI, "translate") != perms.DENY_ACTION;
|
||||
if (perms.testExactPermission(aURI, "translate") == perms.DENY_ACTION) {
|
||||
TranslationHealthReport.recordAutoRejectedTranslationOffer();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
showTranslationUI: function(aDetectedLanguage) {
|
||||
|
@ -298,6 +305,20 @@ let TranslationHealthReport = {
|
|||
this._withProvider(provider => provider.recordMissedTranslationOpportunity(language));
|
||||
},
|
||||
|
||||
/**
|
||||
* Record an automatically rejected translation offer in the health
|
||||
* report. A translation offer is automatically rejected when a user
|
||||
* has previously clicked "Never translate this language" or "Never
|
||||
* translate this site", which results in the infobar not being shown for
|
||||
* the translation opportunity.
|
||||
*
|
||||
* These translation opportunities should still be recorded in addition to
|
||||
* recording the automatic rejection of the offer.
|
||||
*/
|
||||
recordAutoRejectedTranslationOffer: function () {
|
||||
this._withProvider(provider => provider.recordAutoRejectedTranslationOffer());
|
||||
},
|
||||
|
||||
/**
|
||||
* Record a translation in the health report.
|
||||
* @param langFrom
|
||||
|
@ -413,6 +434,7 @@ TranslationMeasurement1.prototype = Object.freeze({
|
|||
showOriginalContent: DAILY_COUNTER_FIELD,
|
||||
detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD,
|
||||
showTranslationUI: DAILY_LAST_NUMERIC_FIELD,
|
||||
autoRejectedTranslationOffer: DAILY_COUNTER_FIELD,
|
||||
},
|
||||
|
||||
shouldIncludeField: function (field) {
|
||||
|
@ -511,6 +533,15 @@ TranslationProvider.prototype = Object.freeze({
|
|||
}.bind(this));
|
||||
},
|
||||
|
||||
recordAutoRejectedTranslationOffer: function (date=new Date()) {
|
||||
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
|
||||
TranslationMeasurement1.prototype.version);
|
||||
|
||||
return this._enqueueTelemetryStorageTask(function* recordTask() {
|
||||
yield m.incrementDailyCounter("autoRejectedTranslationOffer", date);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
recordTranslation: function (langFrom, langTo, numCharacters, date=new Date()) {
|
||||
let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
|
||||
TranslationMeasurement1.prototype.version);
|
||||
|
|
|
@ -36,7 +36,8 @@ let MetricsChecker = {
|
|||
showOriginal: day.get("showOriginalContent") || 0,
|
||||
detectedLanguageChangedBefore: day.get("detectedLanguageChangedBefore") || 0,
|
||||
detectedLanguageChangeAfter: day.get("detectedLanguageChangedAfter") || 0,
|
||||
targetLanguageChanged: day.get("targetLanguageChanged") || 0
|
||||
targetLanguageChanged: day.get("targetLanguageChanged") || 0,
|
||||
autoRejectedOffers: day.get("autoRejectedTranslationOffer") || 0
|
||||
};
|
||||
this._metricsTime = metricsTime;
|
||||
}),
|
||||
|
@ -159,6 +160,19 @@ add_task(function* test_language_change() {
|
|||
});
|
||||
});
|
||||
|
||||
add_task(function* test_never_offer_translation() {
|
||||
Services.prefs.setCharPref("browser.translation.neverForLanguages", "fr");
|
||||
|
||||
let tab = yield offerTranslatationFor("<h1>Hallo Welt!</h1>", "fr");
|
||||
|
||||
yield MetricsChecker.checkAdditions({
|
||||
autoRejectedOffers: 1,
|
||||
});
|
||||
|
||||
gBrowser.removeTab(tab);
|
||||
Services.prefs.clearUserPref("browser.translation.neverForLanguages")
|
||||
});
|
||||
|
||||
function getInfobarElement(browser, anonid) {
|
||||
let notif = browser.translationUI
|
||||
.notificationBox.getNotificationWithValue("translation");
|
||||
|
|
|
@ -232,6 +232,11 @@ add_task(function* test_show_original() {
|
|||
yield test_simple_counter("recordShowOriginalContent", "showOriginalContent");
|
||||
});
|
||||
|
||||
add_task(function* test_show_original() {
|
||||
yield test_simple_counter("recordAutoRejectedTranslationOffer",
|
||||
"autoRejectedTranslationOffer");
|
||||
});
|
||||
|
||||
add_task(function* test_collect_daily() {
|
||||
let storage = yield Metrics.Storage("translation");
|
||||
let provider = new TranslationProvider();
|
||||
|
@ -291,6 +296,8 @@ add_task(function* test_healthreporter_json() {
|
|||
|
||||
yield provider.recordDeniedTranslationOffer();
|
||||
|
||||
yield provider.recordAutoRejectedTranslationOffer();
|
||||
|
||||
yield provider.recordShowOriginalContent();
|
||||
|
||||
yield reporter.collectMeasurements();
|
||||
|
@ -329,6 +336,9 @@ add_task(function* test_healthreporter_json() {
|
|||
Assert.ok("deniedTranslationOffer" in translations);
|
||||
Assert.equal(translations["deniedTranslationOffer"], 1);
|
||||
|
||||
Assert.ok("autoRejectedTranslationOffer" in translations);
|
||||
Assert.equal(translations["autoRejectedTranslationOffer"], 1);
|
||||
|
||||
Assert.ok("showOriginalContent" in translations);
|
||||
Assert.equal(translations["showOriginalContent"], 1);
|
||||
} finally {
|
||||
|
@ -357,6 +367,8 @@ add_task(function* test_healthreporter_json2() {
|
|||
|
||||
yield provider.recordDeniedTranslationOffer();
|
||||
|
||||
yield provider.recordAutoRejectedTranslationOffer();
|
||||
|
||||
yield provider.recordShowOriginalContent();
|
||||
|
||||
yield reporter.collectMeasurements();
|
||||
|
@ -378,6 +390,7 @@ add_task(function* test_healthreporter_json2() {
|
|||
Assert.ok(!("detectedLanguageChangedBefore" in translations));
|
||||
Assert.ok(!("detectedLanguageChangedAfter" in translations));
|
||||
Assert.ok(!("deniedTranslationOffer" in translations));
|
||||
Assert.ok(!("autoRejectedTranslationOffer" in translations));
|
||||
Assert.ok(!("showOriginalContent" in translations));
|
||||
} finally {
|
||||
reporter._shutdown();
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
const promise = require("devtools/toolkit/deprecated-sync-thenables");
|
||||
|
||||
loader.lazyGetter(this, "EventEmitter", () => require("devtools/toolkit/event-emitter"));
|
||||
loader.lazyGetter(this, "AutocompletePopup", () => require("devtools/shared/autocomplete-popup").AutocompletePopup);
|
||||
|
||||
// Maximum number of selector suggestions shown in the panel.
|
||||
|
@ -14,6 +14,10 @@ const MAX_SUGGESTIONS = 15;
|
|||
/**
|
||||
* Converts any input box on a page to a CSS selector search and suggestion box.
|
||||
*
|
||||
* Emits 'processing-done' event when it is done processing the current
|
||||
* keypress, search request or selection from the list, whether that led to a
|
||||
* search or not.
|
||||
*
|
||||
* @constructor
|
||||
* @param InspectorPanel aInspector
|
||||
* The InspectorPanel whose `walker` attribute should be used for
|
||||
|
@ -61,6 +65,7 @@ function SelectorSearch(aInspector, aInputNode) {
|
|||
// For testing, we need to be able to wait for the most recent node request
|
||||
// to finish. Tests can watch this promise for that.
|
||||
this._lastQuery = promise.resolve(null);
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
exports.SelectorSearch = SelectorSearch;
|
||||
|
@ -183,6 +188,7 @@ SelectorSearch.prototype = {
|
|||
_onHTMLSearch: function() {
|
||||
let query = this.searchBox.value;
|
||||
if (query == this._lastSearched) {
|
||||
this.emit("processing-done");
|
||||
return;
|
||||
}
|
||||
this._lastSearched = query;
|
||||
|
@ -196,6 +202,7 @@ SelectorSearch.prototype = {
|
|||
if (this.searchPopup.isOpen) {
|
||||
this.searchPopup.hidePopup();
|
||||
}
|
||||
this.emit("processing-done");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -258,7 +265,7 @@ SelectorSearch.prototype = {
|
|||
}
|
||||
this.searchBox.classList.add("devtools-no-search-result");
|
||||
return this.showSuggestions();
|
||||
});
|
||||
}).then(() => this.emit("processing-done"));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -332,7 +339,10 @@ SelectorSearch.prototype = {
|
|||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
if (this._searchResults && this._searchResults.length > 0) {
|
||||
this._lastQuery = this._selectResult(this._searchIndex);
|
||||
this._lastQuery = this._selectResult(this._searchIndex).then(() => this.emit("processing-done"));
|
||||
}
|
||||
else {
|
||||
this.emit("processing-done");
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -393,6 +403,7 @@ SelectorSearch.prototype = {
|
|||
this._onHTMLSearch();
|
||||
break;
|
||||
}
|
||||
this.emit("processing-done");
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -48,8 +48,7 @@ skip-if = true # Bug 1028609
|
|||
[browser_inspector_search-02.js]
|
||||
[browser_inspector_search-03.js]
|
||||
[browser_inspector_select-last-selected.js]
|
||||
# [browser_inspector_search-navigation.js]
|
||||
# Disabled for too many intermittent failures (bug 851349)
|
||||
[browser_inspector_search-navigation.js]
|
||||
[browser_inspector_sidebarstate.js]
|
||||
[browser_inspector_switch-to-inspector-on-pick.js]
|
||||
[browser_inspector_update-on-navigation.js]
|
||||
|
|
|
@ -1,20 +1,13 @@
|
|||
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
function test()
|
||||
{
|
||||
requestLongerTimeout(2);
|
||||
// Check that searchbox value is correct when suggestions popup is navigated
|
||||
// with keyboard.
|
||||
|
||||
let inspector, searchBox, state, panel;
|
||||
let panelOpeningStates = [0, 3, 9, 14, 17];
|
||||
let panelClosingStates = [2, 8, 13, 16];
|
||||
|
||||
// The various states of the inspector: [key, query]
|
||||
// [
|
||||
// what key to press,
|
||||
// what should be the text in the searchbox
|
||||
// ]
|
||||
let keyStates = [
|
||||
// Test data as pairs of [key to press, expected content of searchbox].
|
||||
const KEY_STATES = [
|
||||
["d", "d"],
|
||||
["i", "di"],
|
||||
["v", "div"],
|
||||
|
@ -25,32 +18,32 @@ function test()
|
|||
["VK_BACK_SPACE", "div.l"],
|
||||
["VK_TAB", "div.l1"],
|
||||
[" ", "div.l1 "],
|
||||
["VK_UP", "div.l1 DIV"],
|
||||
["VK_UP", "div.l1 DIV"],
|
||||
[".", "div.l1 DIV."],
|
||||
["VK_TAB", "div.l1 DIV.c1"],
|
||||
["VK_BACK_SPACE", "div.l1 DIV.c"],
|
||||
["VK_BACK_SPACE", "div.l1 DIV."],
|
||||
["VK_BACK_SPACE", "div.l1 DIV"],
|
||||
["VK_BACK_SPACE", "div.l1 DI"],
|
||||
["VK_BACK_SPACE", "div.l1 D"],
|
||||
["VK_UP", "div.l1 div"],
|
||||
["VK_UP", "div.l1 div"],
|
||||
[".", "div.l1 div."],
|
||||
["VK_TAB", "div.l1 div.c1"],
|
||||
["VK_BACK_SPACE", "div.l1 div.c"],
|
||||
["VK_BACK_SPACE", "div.l1 div."],
|
||||
["VK_BACK_SPACE", "div.l1 div"],
|
||||
["VK_BACK_SPACE", "div.l1 di"],
|
||||
["VK_BACK_SPACE", "div.l1 d"],
|
||||
["VK_BACK_SPACE", "div.l1 "],
|
||||
["VK_UP", "div.l1 DIV"],
|
||||
["VK_BACK_SPACE", "div.l1 DI"],
|
||||
["VK_BACK_SPACE", "div.l1 D"],
|
||||
["VK_UP", "div.l1 div"],
|
||||
["VK_BACK_SPACE", "div.l1 di"],
|
||||
["VK_BACK_SPACE", "div.l1 d"],
|
||||
["VK_BACK_SPACE", "div.l1 "],
|
||||
["VK_UP", "div.l1 DIV"],
|
||||
["VK_UP", "div.l1 DIV"],
|
||||
["VK_TAB", "div.l1 DIV"],
|
||||
["VK_BACK_SPACE", "div.l1 DI"],
|
||||
["VK_BACK_SPACE", "div.l1 D"],
|
||||
["VK_UP", "div.l1 div"],
|
||||
["VK_UP", "div.l1 div"],
|
||||
["VK_TAB", "div.l1 div"],
|
||||
["VK_BACK_SPACE", "div.l1 di"],
|
||||
["VK_BACK_SPACE", "div.l1 d"],
|
||||
["VK_BACK_SPACE", "div.l1 "],
|
||||
["VK_DOWN", "div.l1 DIV"],
|
||||
["VK_DOWN", "div.l1 SPAN"],
|
||||
["VK_DOWN", "div.l1 SPAN"],
|
||||
["VK_BACK_SPACE", "div.l1 SPA"],
|
||||
["VK_BACK_SPACE", "div.l1 SP"],
|
||||
["VK_BACK_SPACE", "div.l1 S"],
|
||||
["VK_DOWN", "div.l1 div"],
|
||||
["VK_DOWN", "div.l1 span"],
|
||||
["VK_DOWN", "div.l1 span"],
|
||||
["VK_BACK_SPACE", "div.l1 spa"],
|
||||
["VK_BACK_SPACE", "div.l1 sp"],
|
||||
["VK_BACK_SPACE", "div.l1 s"],
|
||||
["VK_BACK_SPACE", "div.l1 "],
|
||||
["VK_BACK_SPACE", "div.l1"],
|
||||
["VK_BACK_SPACE", "div.l"],
|
||||
|
@ -59,104 +52,22 @@ function test()
|
|||
["VK_BACK_SPACE", "di"],
|
||||
["VK_BACK_SPACE", "d"],
|
||||
["VK_BACK_SPACE", ""],
|
||||
];
|
||||
];
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onload() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
|
||||
waitForFocus(setupTest, content);
|
||||
}, true);
|
||||
const TEST_URL = TEST_URL_ROOT +
|
||||
"doc_inspector_search-suggestions.html";
|
||||
|
||||
content.location = "http://mochi.test:8888/browser/browser/devtools/inspector/test/doc_inspector_search-suggestions.html";
|
||||
let test = asyncTest(function* () {
|
||||
let { inspector } = yield openInspectorForURL(TEST_URL);
|
||||
yield focusSearchBoxUsingShortcut(inspector.panelWin);
|
||||
|
||||
function $(id) {
|
||||
if (id == null) return null;
|
||||
return content.document.getElementById(id);
|
||||
}
|
||||
for (let [key, query] of KEY_STATES) {
|
||||
info("Pressing key " + key + " to get searchbox value as " + query);
|
||||
|
||||
function setupTest()
|
||||
{
|
||||
openInspector(startTest);
|
||||
}
|
||||
|
||||
function startTest(aInspector)
|
||||
{
|
||||
inspector = aInspector;
|
||||
searchBox =
|
||||
inspector.panelWin.document.getElementById("inspector-searchbox");
|
||||
panel = inspector.searchSuggestions.searchPopup._list;
|
||||
|
||||
focusSearchBoxUsingShortcut(inspector.panelWin, function() {
|
||||
searchBox.addEventListener("keypress", checkState, true);
|
||||
panel.addEventListener("keypress", checkState, true);
|
||||
checkStateAndMoveOn(0);
|
||||
});
|
||||
}
|
||||
|
||||
function checkStateAndMoveOn(index) {
|
||||
if (index == keyStates.length) {
|
||||
finishUp();
|
||||
return;
|
||||
}
|
||||
|
||||
let [key, query] = keyStates[index];
|
||||
state = index;
|
||||
|
||||
info("pressing key " + key + " to get searchbox value as " + query);
|
||||
let done = inspector.searchSuggestions.once("processing-done");
|
||||
EventUtils.synthesizeKey(key, {}, inspector.panelWin);
|
||||
}
|
||||
yield done;
|
||||
|
||||
function checkState(event) {
|
||||
if (event && event.keyCode != event.DOM_VK_UP &&
|
||||
event.keyCode != event.DOM_VK_DOWN) {
|
||||
info("Should wait before server sends the qSA response.");
|
||||
inspector.searchSuggestions._lastQuery
|
||||
.then(() => checkState(), () => checkState());
|
||||
return;
|
||||
is(inspector.searchBox.value, query, "The searchbox value is correct.");
|
||||
}
|
||||
if (panelOpeningStates.indexOf(state) != -1 &&
|
||||
!inspector.searchSuggestions.searchPopup.isOpen) {
|
||||
info("Panel is not open, should wait before it shows up.");
|
||||
panel.parentNode.addEventListener("popupshown", function retry() {
|
||||
panel.parentNode.removeEventListener("popupshown", retry, false);
|
||||
info("Panel is visible now");
|
||||
executeSoon(checkState);
|
||||
}, false);
|
||||
return;
|
||||
}
|
||||
else if (panelClosingStates.indexOf(state) != -1 &&
|
||||
panel.parentNode.state != "closed") {
|
||||
info("Panel is open, should wait for it to close.");
|
||||
panel.parentNode.addEventListener("popuphidden", function retry() {
|
||||
panel.parentNode.removeEventListener("popuphidden", retry, false);
|
||||
info("Panel is hidden now");
|
||||
executeSoon(checkState);
|
||||
}, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Using setTimout as the "command" event fires at delay after keypress
|
||||
window.setTimeout(function() {
|
||||
let [key, query] = keyStates[state];
|
||||
|
||||
if (searchBox.value == query) {
|
||||
ok(true, "The suggestion at " + state + "th step on " +
|
||||
"pressing " + key + " key is correct.");
|
||||
}
|
||||
else {
|
||||
info("value is not correct, waiting longer for state " + state +
|
||||
" with panel " + panel.parentNode.state);
|
||||
checkState();
|
||||
return;
|
||||
}
|
||||
checkStateAndMoveOn(state + 1);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function finishUp() {
|
||||
searchBox = null;
|
||||
panel = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -21,11 +21,14 @@ XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
|
|||
this.ContentLinkHandler = {
|
||||
init: function(chromeGlobal) {
|
||||
chromeGlobal.addEventListener("DOMLinkAdded", (event) => {
|
||||
this.onLinkAdded(event, chromeGlobal);
|
||||
this.onLinkEvent(event, chromeGlobal);
|
||||
}, false);
|
||||
chromeGlobal.addEventListener("DOMLinkChanged", (event) => {
|
||||
this.onLinkEvent(event, chromeGlobal);
|
||||
}, false);
|
||||
},
|
||||
|
||||
onLinkAdded: function(event, chromeGlobal) {
|
||||
onLinkEvent: function(event, chromeGlobal) {
|
||||
var link = event.originalTarget;
|
||||
var rel = link.rel && link.rel.toLowerCase();
|
||||
if (!link || !link.ownerDocument || !rel || !link.href)
|
||||
|
@ -47,7 +50,7 @@ this.ContentLinkHandler = {
|
|||
switch (relVal) {
|
||||
case "feed":
|
||||
case "alternate":
|
||||
if (!feedAdded) {
|
||||
if (!feedAdded && event.type == "DOMLinkAdded") {
|
||||
if (!rels.feed && rels.alternate && rels.stylesheet)
|
||||
break;
|
||||
|
||||
|
@ -69,11 +72,11 @@ this.ContentLinkHandler = {
|
|||
if (!uri)
|
||||
break;
|
||||
|
||||
[iconAdded] = chromeGlobal.sendSyncMessage("Link:AddIcon", {url: uri.spec});
|
||||
[iconAdded] = chromeGlobal.sendSyncMessage("Link:SetIcon", {url: uri.spec});
|
||||
}
|
||||
break;
|
||||
case "search":
|
||||
if (!searchAdded) {
|
||||
if (!searchAdded && event.type == "DOMLinkAdded") {
|
||||
var type = link.type && link.type.toLowerCase();
|
||||
type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
|
||||
|
||||
|
|
|
@ -498,7 +498,7 @@ this.OpenGraphBuilder = {
|
|||
return endpointURL;
|
||||
},
|
||||
|
||||
getData: function(browser) {
|
||||
getData: function(browser, target) {
|
||||
let res = {
|
||||
url: this._validateURL(browser, browser.currentURI.spec),
|
||||
title: browser.contentDocument.title,
|
||||
|
@ -507,9 +507,14 @@ this.OpenGraphBuilder = {
|
|||
this._getMetaData(browser, res);
|
||||
this._getLinkData(browser, res);
|
||||
this._getPageData(browser, res);
|
||||
res.microdata = this.getMicrodata(browser, target);
|
||||
return res;
|
||||
},
|
||||
|
||||
getMicrodata: function (browser, target) {
|
||||
return getMicrodata(browser.contentDocument, target);
|
||||
},
|
||||
|
||||
_getMetaData: function(browser, o) {
|
||||
// query for standardized meta data
|
||||
let els = browser.contentDocument
|
||||
|
@ -522,7 +527,14 @@ this.OpenGraphBuilder = {
|
|||
if (!value)
|
||||
continue;
|
||||
value = unescapeService.unescape(value.trim());
|
||||
switch (el.getAttribute("property") || el.getAttribute("name")) {
|
||||
let key = el.getAttribute("property") || el.getAttribute("name");
|
||||
if (!key)
|
||||
continue;
|
||||
// There are a wide array of possible meta tags, expressing articles,
|
||||
// products, etc. so all meta tags are passed through but we touch up the
|
||||
// most common attributes.
|
||||
o[key] = value;
|
||||
switch (key) {
|
||||
case "title":
|
||||
case "og:title":
|
||||
o.title = value;
|
||||
|
@ -577,6 +589,19 @@ this.OpenGraphBuilder = {
|
|||
case "image_src":
|
||||
o.previews.push(url);
|
||||
break;
|
||||
case "alternate":
|
||||
// expressly for oembed support but we're liberal here and will let
|
||||
// other alternate links through. oembed defines an href, supplied by
|
||||
// the site, where you can fetch additional meta data about a page.
|
||||
// We'll let the client fetch the oembed data themselves, but they
|
||||
// need the data from this link.
|
||||
if (!o.alternate)
|
||||
o.alternate = [];
|
||||
o.alternate.push({
|
||||
"type": el.getAttribute("type"),
|
||||
"href": el.getAttribute("href"),
|
||||
"title": el.getAttribute("title")
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -610,3 +635,43 @@ this.OpenGraphBuilder = {
|
|||
return l;
|
||||
}
|
||||
};
|
||||
|
||||
// getMicrodata (and getObject) based on wg algorythm to convert microdata to json
|
||||
// http://www.whatwg.org/specs/web-apps/current-work/multipage/microdata-2.html#json
|
||||
function getMicrodata(document, target) {
|
||||
|
||||
function _getObject(item) {
|
||||
let result = {};
|
||||
if (item.itemType.length)
|
||||
result.types = [i for (i of item.itemType)];
|
||||
if (item.itemId)
|
||||
result.itemId = item.itemid;
|
||||
if (item.properties.length)
|
||||
result.properties = {};
|
||||
for (let elem of item.properties) {
|
||||
let value;
|
||||
if (elem.itemScope)
|
||||
value = _getObject(elem);
|
||||
else if (elem.itemValue)
|
||||
value = elem.itemValue;
|
||||
// handle mis-formatted microdata
|
||||
else if (elem.hasAttribute("content"))
|
||||
value = elem.getAttribute("content");
|
||||
|
||||
for (let prop of elem.itemProp) {
|
||||
if (!result.properties[prop])
|
||||
result.properties[prop] = [];
|
||||
result.properties[prop].push(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
let result = { items: [] };
|
||||
let elms = target ? [target] : document.getItems();
|
||||
for (let el of elms) {
|
||||
if (el.itemScope)
|
||||
result.items.push(_getObject(el));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ _JAVA_HARNESS := \
|
|||
RobocopShare2.java \
|
||||
RobocopUtils.java \
|
||||
PaintedSurface.java \
|
||||
StructuredLogger.java \
|
||||
$(NULL)
|
||||
|
||||
java-harness := $(addprefix $(srcdir)/,$(_JAVA_HARNESS))
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
/* 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/. */
|
||||
|
||||
package org.mozilla.gecko;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
// This implements the structured logging API described here: http://mozbase.readthedocs.org/en/latest/mozlog_structured.html
|
||||
public class StructuredLogger {
|
||||
private final static HashSet<String> validTestStatus = new HashSet<String>(Arrays.asList("PASS", "FAIL", "TIMEOUT", "NOTRUN", "ASSERT"));
|
||||
private final static HashSet<String> validTestEnd = new HashSet<String>(Arrays.asList("PASS", "FAIL", "OK", "ERROR", "TIMEOUT",
|
||||
"CRASH", "ASSERT", "SKIP"));
|
||||
|
||||
private String mName;
|
||||
private String mComponent;
|
||||
private LoggerCallback mCallback;
|
||||
|
||||
static public interface LoggerCallback {
|
||||
public void call(String output);
|
||||
}
|
||||
|
||||
/* A default logger callback that prints the JSON output to stdout.
|
||||
* This is not to be used in robocop as we write to a log file. */
|
||||
static class StandardLoggerCallback implements LoggerCallback {
|
||||
public void call(String output) {
|
||||
System.out.println(output);
|
||||
}
|
||||
}
|
||||
|
||||
public StructuredLogger(String name, String component, LoggerCallback callback) {
|
||||
mName = name;
|
||||
mComponent = component;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public StructuredLogger(String name, String component) {
|
||||
this(name, component, new StandardLoggerCallback());
|
||||
}
|
||||
|
||||
public StructuredLogger(String name) {
|
||||
this(name, null, new StandardLoggerCallback());
|
||||
}
|
||||
|
||||
public void suiteStart(List<String> tests, Map<String, Object> runInfo) {
|
||||
HashMap<String, Object> data = new HashMap<String, Object>();
|
||||
data.put("tests", tests);
|
||||
if (runInfo != null) {
|
||||
data.put("run_info", runInfo);
|
||||
}
|
||||
this.logData("suite_start", data);
|
||||
}
|
||||
|
||||
public void suiteStart(List<String> tests) {
|
||||
this.suiteStart(tests, null);
|
||||
}
|
||||
|
||||
public void suiteEnd() {
|
||||
this.logData("suite_end");
|
||||
}
|
||||
|
||||
public void testStart(String test) {
|
||||
HashMap<String, Object> data = new HashMap<String, Object>();
|
||||
data.put("test", test);
|
||||
this.logData("test_start", data);
|
||||
}
|
||||
|
||||
public void testStatus(String test, String subtest, String status, String expected, String message) {
|
||||
status = status.toUpperCase();
|
||||
if (!StructuredLogger.validTestStatus.contains(status)) {
|
||||
throw new IllegalArgumentException("Unrecognized status: " + status);
|
||||
}
|
||||
|
||||
HashMap<String, Object> data = new HashMap<String, Object>();
|
||||
data.put("test", test);
|
||||
data.put("subtest", subtest);
|
||||
data.put("status", status);
|
||||
|
||||
if (message != null) {
|
||||
data.put("message", message);
|
||||
}
|
||||
if (!expected.equals(status)) {
|
||||
data.put("expected", expected);
|
||||
}
|
||||
|
||||
this.logData("test_status", data);
|
||||
}
|
||||
|
||||
public void testStatus(String test, String subtest, String status, String message) {
|
||||
this.testStatus(test, subtest, status, "PASS", message);
|
||||
}
|
||||
|
||||
public void testEnd(String test, String status, String expected, String message, Map<String, Object> extra) {
|
||||
status = status.toUpperCase();
|
||||
if (!StructuredLogger.validTestEnd.contains(status)) {
|
||||
throw new IllegalArgumentException("Unrecognized status: " + status);
|
||||
}
|
||||
|
||||
HashMap<String, Object> data = new HashMap<String, Object>();
|
||||
data.put("test", test);
|
||||
data.put("status", status);
|
||||
|
||||
if (message != null) {
|
||||
data.put("message", message);
|
||||
}
|
||||
if (extra != null) {
|
||||
data.put("extra", extra);
|
||||
}
|
||||
if (!expected.equals(status) && !status.equals("SKIP")) {
|
||||
data.put("expected", expected);
|
||||
}
|
||||
|
||||
this.logData("test_end", data);
|
||||
}
|
||||
|
||||
public void testEnd(String test, String status, String expected, String message) {
|
||||
this.testEnd(test, status, expected, message, null);
|
||||
}
|
||||
|
||||
public void testEnd(String test, String status, String message) {
|
||||
this.testEnd(test, status, "OK", message, null);
|
||||
}
|
||||
|
||||
|
||||
public void debug(String message) {
|
||||
this.log("debug", message);
|
||||
}
|
||||
|
||||
public void info(String message) {
|
||||
this.log("info", message);
|
||||
}
|
||||
|
||||
public void warning(String message) {
|
||||
this.log("warning", message);
|
||||
}
|
||||
|
||||
public void error(String message) {
|
||||
this.log("error", message);
|
||||
}
|
||||
|
||||
public void critical(String message) {
|
||||
this.log("critical", message);
|
||||
}
|
||||
|
||||
private void log(String level, String message) {
|
||||
HashMap<String, Object> data = new HashMap<String, Object>();
|
||||
data.put("message", message);
|
||||
data.put("level", level);
|
||||
this.logData("log", data);
|
||||
}
|
||||
|
||||
private HashMap<String, Object> makeLogData(String action, Map<String, Object> data) {
|
||||
HashMap<String, Object> allData = new HashMap<String, Object>();
|
||||
allData.put("action", action);
|
||||
allData.put("time", System.currentTimeMillis());
|
||||
allData.put("thread", JSONObject.NULL);
|
||||
allData.put("pid", JSONObject.NULL);
|
||||
allData.put("source", mName);
|
||||
if (mComponent != null) {
|
||||
allData.put("component", mComponent);
|
||||
}
|
||||
|
||||
allData.putAll(data);
|
||||
|
||||
return allData;
|
||||
}
|
||||
|
||||
private void logData(String action, Map<String, Object> data) {
|
||||
HashMap<String, Object> logData = this.makeLogData(action, data);
|
||||
JSONObject jsonObject = new JSONObject(logData);
|
||||
mCallback.call(jsonObject.toString());
|
||||
}
|
||||
|
||||
private void logData(String action) {
|
||||
this.logData(action, new HashMap<String, Object>());
|
||||
}
|
||||
|
||||
}
|
|
@ -27,6 +27,7 @@
|
|||
#include "nsMathUtils.h"
|
||||
#include "nsTArrayForwardDeclare.h"
|
||||
#include "Units.h"
|
||||
#include "mozilla/dom/AutocompleteInfoBinding.h"
|
||||
|
||||
#if defined(XP_WIN)
|
||||
// Undefine LoadImage to prevent naming conflict with Windows.
|
||||
|
@ -2045,8 +2046,22 @@ public:
|
|||
*
|
||||
* @return whether aAttr was valid and can be cached.
|
||||
*/
|
||||
static AutocompleteAttrState SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
|
||||
nsAString& aResult);
|
||||
static AutocompleteAttrState
|
||||
SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
|
||||
nsAString& aResult,
|
||||
AutocompleteAttrState aCachedState =
|
||||
eAutocompleteAttrState_Unknown);
|
||||
|
||||
/* Variation that is used to retrieve a dictionary of the parts of the
|
||||
* autocomplete attribute.
|
||||
*
|
||||
* @return whether aAttr was valid and can be cached.
|
||||
*/
|
||||
static AutocompleteAttrState
|
||||
SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
|
||||
mozilla::dom::AutocompleteInfo& aInfo,
|
||||
AutocompleteAttrState aCachedState =
|
||||
eAutocompleteAttrState_Unknown);
|
||||
|
||||
/**
|
||||
* This will parse aSource, to extract the value of the pseudo attribute
|
||||
|
@ -2205,8 +2220,9 @@ private:
|
|||
static void* AllocClassMatchingInfo(nsINode* aRootNode,
|
||||
const nsString* aClasses);
|
||||
|
||||
// Fills in aInfo with the tokens from the supplied autocomplete attribute.
|
||||
static AutocompleteAttrState InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
|
||||
nsAString& aResult);
|
||||
mozilla::dom::AutocompleteInfo& aInfo);
|
||||
|
||||
static nsIXPConnect *sXPConnect;
|
||||
|
||||
|
|
|
@ -766,17 +766,73 @@ nsContentUtils::IsAutocompleteEnabled(nsIDOMHTMLInputElement* aInput)
|
|||
|
||||
nsContentUtils::AutocompleteAttrState
|
||||
nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
|
||||
nsAString& aResult)
|
||||
nsAString& aResult,
|
||||
AutocompleteAttrState aCachedState)
|
||||
{
|
||||
AutocompleteAttrState state = InternalSerializeAutocompleteAttribute(aAttr, aResult);
|
||||
if (state == eAutocompleteAttrState_Valid) {
|
||||
ASCIIToLower(aResult);
|
||||
} else {
|
||||
aResult.Truncate();
|
||||
if (!aAttr ||
|
||||
aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
|
||||
return aCachedState;
|
||||
}
|
||||
|
||||
if (aCachedState == nsContentUtils::eAutocompleteAttrState_Valid) {
|
||||
uint32_t atomCount = aAttr->GetAtomCount();
|
||||
for (uint32_t i = 0; i < atomCount; i++) {
|
||||
if (i != 0) {
|
||||
aResult.Append(' ');
|
||||
}
|
||||
aResult.Append(nsDependentAtomString(aAttr->AtomAt(i)));
|
||||
}
|
||||
nsContentUtils::ASCIIToLower(aResult);
|
||||
return aCachedState;
|
||||
}
|
||||
|
||||
aResult.Truncate();
|
||||
|
||||
mozilla::dom::AutocompleteInfo info;
|
||||
AutocompleteAttrState state =
|
||||
InternalSerializeAutocompleteAttribute(aAttr, info);
|
||||
if (state == eAutocompleteAttrState_Valid) {
|
||||
// Concatenate the info fields.
|
||||
aResult = info.mSection;
|
||||
|
||||
if (!info.mAddressType.IsEmpty()) {
|
||||
if (!aResult.IsEmpty()) {
|
||||
aResult += ' ';
|
||||
}
|
||||
aResult += info.mAddressType;
|
||||
}
|
||||
|
||||
if (!info.mContactType.IsEmpty()) {
|
||||
if (!aResult.IsEmpty()) {
|
||||
aResult += ' ';
|
||||
}
|
||||
aResult += info.mContactType;
|
||||
}
|
||||
|
||||
if (!info.mFieldName.IsEmpty()) {
|
||||
if (!aResult.IsEmpty()) {
|
||||
aResult += ' ';
|
||||
}
|
||||
aResult += info.mFieldName;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
nsContentUtils::AutocompleteAttrState
|
||||
nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
|
||||
mozilla::dom::AutocompleteInfo& aInfo,
|
||||
AutocompleteAttrState aCachedState)
|
||||
{
|
||||
if (!aAttr ||
|
||||
aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
|
||||
return aCachedState;
|
||||
}
|
||||
|
||||
return InternalSerializeAutocompleteAttribute(aAttr, aInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to validate the @autocomplete tokens.
|
||||
*
|
||||
|
@ -784,7 +840,7 @@ nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
|
|||
*/
|
||||
nsContentUtils::AutocompleteAttrState
|
||||
nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
|
||||
nsAString& aResult)
|
||||
mozilla::dom::AutocompleteInfo& aInfo)
|
||||
{
|
||||
// No sandbox attribute so we are done
|
||||
if (!aAttrVal) {
|
||||
|
@ -801,6 +857,7 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
|
|||
AutocompleteCategory category;
|
||||
nsAttrValue enumValue;
|
||||
|
||||
nsAutoString str;
|
||||
bool result = enumValue.ParseEnumValue(tokenString, kAutocompleteFieldNameTable, false);
|
||||
if (result) {
|
||||
// Off/Automatic/Normal categories.
|
||||
|
@ -809,7 +866,9 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
|
|||
if (numTokens > 1) {
|
||||
return eAutocompleteAttrState_Invalid;
|
||||
}
|
||||
enumValue.ToString(aResult);
|
||||
enumValue.ToString(str);
|
||||
ASCIIToLower(str);
|
||||
aInfo.mFieldName.Assign(str);
|
||||
return eAutocompleteAttrState_Valid;
|
||||
}
|
||||
|
||||
|
@ -837,7 +896,9 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
|
|||
category = eAutocompleteCategory_CONTACT;
|
||||
}
|
||||
|
||||
enumValue.ToString(aResult);
|
||||
enumValue.ToString(str);
|
||||
ASCIIToLower(str);
|
||||
aInfo.mFieldName.Assign(str);
|
||||
|
||||
// We are done if this was the only token.
|
||||
if (numTokens == 1) {
|
||||
|
@ -851,10 +912,10 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
|
|||
nsAttrValue contactFieldHint;
|
||||
result = contactFieldHint.ParseEnumValue(tokenString, kAutocompleteContactFieldHintTable, false);
|
||||
if (result) {
|
||||
aResult.Insert(' ', 0);
|
||||
nsAutoString contactFieldHintString;
|
||||
contactFieldHint.ToString(contactFieldHintString);
|
||||
aResult.Insert(contactFieldHintString, 0);
|
||||
ASCIIToLower(contactFieldHintString);
|
||||
aInfo.mContactType.Assign(contactFieldHintString);
|
||||
if (index == 0) {
|
||||
return eAutocompleteAttrState_Valid;
|
||||
}
|
||||
|
@ -866,16 +927,21 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
|
|||
// Check for billing/shipping tokens
|
||||
nsAttrValue fieldHint;
|
||||
if (fieldHint.ParseEnumValue(tokenString, kAutocompleteFieldHintTable, false)) {
|
||||
aResult.Insert(' ', 0);
|
||||
nsString fieldHintString;
|
||||
fieldHint.ToString(fieldHintString);
|
||||
aResult.Insert(fieldHintString, 0);
|
||||
ASCIIToLower(fieldHintString);
|
||||
aInfo.mAddressType.Assign(fieldHintString);
|
||||
if (index == 0) {
|
||||
return eAutocompleteAttrState_Valid;
|
||||
}
|
||||
--index;
|
||||
}
|
||||
|
||||
// Clear the fields as the autocomplete attribute is invalid.
|
||||
aInfo.mAddressType.Truncate();
|
||||
aInfo.mContactType.Truncate();
|
||||
aInfo.mFieldName.Truncate();
|
||||
|
||||
return eAutocompleteAttrState_Invalid;
|
||||
}
|
||||
|
||||
|
|
|
@ -1529,23 +1529,10 @@ HTMLInputElement::GetAutocomplete(nsAString& aValue)
|
|||
{
|
||||
aValue.Truncate(0);
|
||||
const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
|
||||
if (!attributeVal ||
|
||||
mAutocompleteAttrState == nsContentUtils::eAutocompleteAttrState_Invalid) {
|
||||
return NS_OK;
|
||||
}
|
||||
if (mAutocompleteAttrState == nsContentUtils::eAutocompleteAttrState_Valid) {
|
||||
uint32_t atomCount = attributeVal->GetAtomCount();
|
||||
for (uint32_t i = 0; i < atomCount; i++) {
|
||||
if (i != 0) {
|
||||
aValue.Append(' ');
|
||||
}
|
||||
aValue.Append(nsDependentAtomString(attributeVal->AtomAt(i)));
|
||||
}
|
||||
nsContentUtils::ASCIIToLower(aValue);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mAutocompleteAttrState = nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue);
|
||||
mAutocompleteAttrState =
|
||||
nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aValue,
|
||||
mAutocompleteAttrState);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -1555,6 +1542,15 @@ HTMLInputElement::SetAutocomplete(const nsAString& aValue)
|
|||
return SetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, nullptr, aValue, true);
|
||||
}
|
||||
|
||||
void
|
||||
HTMLInputElement::GetAutocompleteInfo(AutocompleteInfo& aInfo)
|
||||
{
|
||||
const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete);
|
||||
mAutocompleteAttrState =
|
||||
nsContentUtils::SerializeAutocompleteAttribute(attributeVal, aInfo,
|
||||
mAutocompleteAttrState);
|
||||
}
|
||||
|
||||
int32_t
|
||||
HTMLInputElement::TabIndexDefault()
|
||||
{
|
||||
|
|
|
@ -370,6 +370,8 @@ public:
|
|||
SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv);
|
||||
}
|
||||
|
||||
void GetAutocompleteInfo(AutocompleteInfo& aInfo);
|
||||
|
||||
bool Autofocus() const
|
||||
{
|
||||
return GetBoolAttr(nsGkAtoms::autofocus);
|
||||
|
|
|
@ -320,6 +320,7 @@ HTMLLinkElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
|
|||
// to get updated information about the visitedness from Link.
|
||||
if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
|
||||
Link::ResetLinkState(!!aNotify, true);
|
||||
CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(rv) && aNameSpaceID == kNameSpaceID_None &&
|
||||
|
@ -382,6 +383,7 @@ HTMLLinkElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute,
|
|||
// to get updated information about the visitedness from Link.
|
||||
if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
|
||||
Link::ResetLinkState(!!aNotify, false);
|
||||
CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
|
||||
}
|
||||
|
||||
return rv;
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[DEFAULT]
|
||||
[test_autocompleteinfo.html]
|
|
@ -0,0 +1,99 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Test getAutocompleteInfo() on <input>
|
||||
-->
|
||||
<head>
|
||||
<title>Test for getAutocompleteInfo()</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
<form>
|
||||
<input id="input"/>
|
||||
</form>
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script>
|
||||
"use strict";
|
||||
|
||||
var values = [
|
||||
// Missing or empty attribute
|
||||
[undefined, {}],
|
||||
["", {}],
|
||||
|
||||
// One token
|
||||
["on", {fieldName: "on" }],
|
||||
["On", {fieldName: "on" }],
|
||||
["off", {fieldName: "off" } ],
|
||||
["username", {fieldName: "username" }],
|
||||
[" username ", {fieldName: "username" }],
|
||||
["foobar", {}],
|
||||
|
||||
// Two tokens
|
||||
["on off", {}],
|
||||
["off on", {}],
|
||||
["username tel", {}],
|
||||
["tel username ", {}],
|
||||
[" username tel ", {}],
|
||||
["tel mobile", {}],
|
||||
["tel shipping", {}],
|
||||
["shipping tel", {addressType: "shipping", fieldName: "tel"}],
|
||||
["shipPING tel", {addressType: "shipping", fieldName: "tel"}],
|
||||
["mobile tel", {contactType: "mobile", fieldName: "tel"}],
|
||||
[" MoBiLe TeL ", {contactType: "mobile", fieldName: "tel"}],
|
||||
["XXX tel", {}],
|
||||
["XXX username", {}],
|
||||
|
||||
// Three tokens
|
||||
["billing invalid tel", {}],
|
||||
["___ mobile tel", {}],
|
||||
["mobile foo tel", {}],
|
||||
["mobile tel foo", {}],
|
||||
["tel mobile billing", {}],
|
||||
["billing mobile tel", {addressType: "billing", contactType: "mobile", fieldName: "tel"}],
|
||||
[" BILLing MoBiLE tEl ", {addressType: "billing", contactType: "mobile", fieldName: "tel"}],
|
||||
["billing home tel", {addressType: "billing", contactType: "home", fieldName: "tel"}],
|
||||
|
||||
// Four tokens (invalid)
|
||||
["billing billing mobile tel", {}],
|
||||
|
||||
// Five tokens (invalid)
|
||||
["billing billing billing mobile tel", {}],
|
||||
];
|
||||
|
||||
function start() {
|
||||
const fieldid = "input";
|
||||
var field = document.getElementById(fieldid);
|
||||
for (var test of values) {
|
||||
if (typeof(test[0]) === "undefined")
|
||||
field.removeAttribute("autocomplete");
|
||||
else
|
||||
field.setAttribute("autocomplete", test[0]);
|
||||
|
||||
var info = field.getAutocompleteInfo();
|
||||
|
||||
is(info.section, "section" in test[1] ? test[1].section : "",
|
||||
"Checking autocompleteInfo.section for " + fieldid + ": " + test[0]);
|
||||
is(info.addressType, "addressType" in test[1] ? test[1].addressType : "",
|
||||
"Checking autocompleteInfo.addressType for " + fieldid + ": " + test[0]);
|
||||
is(info.contactType, "contactType" in test[1] ? test[1].contactType : "",
|
||||
"Checking autocompleteInfo.contactType for " + fieldid + ": " + test[0]);
|
||||
is(info.fieldName, "fieldName" in test[1] ? test[1].fieldName : "",
|
||||
"Checking autocompleteInfo.fieldName for " + fieldid + ": " + test[0]);
|
||||
|
||||
}
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.forms.autocomplete.experimental", true]]}, start);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
MOCHITEST_MANIFESTS += ['forms/mochitest.ini', 'mochitest.ini']
|
||||
|
||||
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
|
||||
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini', 'forms/chrome.ini']
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['browser.ini']
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This dictionary is used for the input, textarea and select element's
|
||||
* getAutocompleteInfo method.
|
||||
*/
|
||||
|
||||
dictionary AutocompleteInfo {
|
||||
DOMString section = "";
|
||||
DOMString addressType = "";
|
||||
DOMString contactType = "";
|
||||
DOMString fieldName = "";
|
||||
};
|
|
@ -167,6 +167,9 @@ partial interface HTMLInputElement {
|
|||
readonly attribute HTMLInputElement? ownerNumberControl;
|
||||
|
||||
boolean mozIsTextField(boolean aExcludePassword);
|
||||
|
||||
[ChromeOnly]
|
||||
AutocompleteInfo getAutocompleteInfo();
|
||||
};
|
||||
|
||||
partial interface HTMLInputElement {
|
||||
|
|
|
@ -39,6 +39,7 @@ WEBIDL_FILES = [
|
|||
'AudioStreamTrack.webidl',
|
||||
'AudioTrack.webidl',
|
||||
'AudioTrackList.webidl',
|
||||
'AutocompleteInfo.webidl',
|
||||
'BarProp.webidl',
|
||||
'BatteryManager.webidl',
|
||||
'BeforeUnloadEvent.webidl',
|
||||
|
|
|
@ -2299,8 +2299,10 @@ public class BrowserApp extends GeckoApp
|
|||
|
||||
@Override
|
||||
public void openOptionsMenu() {
|
||||
if (!hasTabsSideBar() && areTabsShown())
|
||||
if (areTabsShown()) {
|
||||
mTabsPanel.showMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
// Scroll custom menu to the top
|
||||
if (mMenuPanel != null)
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
android:padding="@dimen/browser_toolbar_button_padding"
|
||||
android:src="@drawable/menu_tabs"
|
||||
android:contentDescription="@string/menu"
|
||||
android:background="@drawable/action_bar_button"/>
|
||||
android:background="@drawable/action_bar_button"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</merge>
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
android:padding="@dimen/browser_toolbar_button_padding"
|
||||
android:src="@drawable/menu_tabs"
|
||||
android:contentDescription="@string/menu"
|
||||
android:background="@drawable/action_bar_button"/>
|
||||
android:background="@drawable/action_bar_button"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</merge>
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.mozilla.gecko.Telemetry;
|
|||
import org.mozilla.gecko.TelemetryContract;
|
||||
import org.mozilla.gecko.animation.PropertyAnimator;
|
||||
import org.mozilla.gecko.animation.ViewHelper;
|
||||
import org.mozilla.gecko.util.HardwareUtils;
|
||||
import org.mozilla.gecko.widget.GeckoPopupMenu;
|
||||
import org.mozilla.gecko.widget.IconTabWidget;
|
||||
|
||||
|
@ -172,6 +173,12 @@ public class TabsPanel extends LinearLayout
|
|||
mMenuButton.setOnClickListener(new Button.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
showMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void showMenu() {
|
||||
final Menu menu = mPopupMenu.getMenu();
|
||||
|
||||
// Each panel has a "+" shortcut button, so don't show it for that panel.
|
||||
|
@ -184,9 +191,6 @@ public class TabsPanel extends LinearLayout
|
|||
|
||||
mPopupMenu.show();
|
||||
}
|
||||
});
|
||||
mPopupMenu.setAnchor(mMenuButton);
|
||||
}
|
||||
|
||||
private void addTab() {
|
||||
if (mCurrentPanel == Panel.NORMAL_TABS) {
|
||||
|
@ -427,7 +431,7 @@ public class TabsPanel extends LinearLayout
|
|||
|
||||
mAddTab.setVisibility(View.INVISIBLE);
|
||||
|
||||
mMenuButton.setVisibility(View.INVISIBLE);
|
||||
mMenuButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (mFooter != null)
|
||||
mFooter.setVisibility(View.VISIBLE);
|
||||
|
@ -435,8 +439,13 @@ public class TabsPanel extends LinearLayout
|
|||
mAddTab.setVisibility(View.VISIBLE);
|
||||
mAddTab.setImageLevel(index);
|
||||
|
||||
|
||||
if (!HardwareUtils.hasMenuButton()) {
|
||||
mMenuButton.setVisibility(View.VISIBLE);
|
||||
mMenuButton.setEnabled(true);
|
||||
mPopupMenu.setAnchor(mMenuButton);
|
||||
} else {
|
||||
mPopupMenu.setAnchor(mAddTab);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSideBar()) {
|
||||
|
|
|
@ -4157,6 +4157,13 @@ Tab.prototype = {
|
|||
if (!sameDocument) {
|
||||
// XXX This code assumes that this is the earliest hook we have at which
|
||||
// browser.contentDocument is changed to the new document we're loading
|
||||
|
||||
// We have a new browser and a new window, so the old browserWidth and
|
||||
// browserHeight are no longer valid. We need to force-set the browser
|
||||
// size to ensure it sets the CSS viewport size before the document
|
||||
// has a chance to check it.
|
||||
this.setBrowserSize(kDefaultCSSViewportWidth, kDefaultCSSViewportHeight, true);
|
||||
|
||||
this.contentDocumentIsDisplayed = false;
|
||||
this.hasTouchListener = false;
|
||||
} else {
|
||||
|
@ -4460,10 +4467,12 @@ Tab.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
setBrowserSize: function(aWidth, aHeight) {
|
||||
setBrowserSize: function(aWidth, aHeight, aForce) {
|
||||
if (!aForce) {
|
||||
if (fuzzyEquals(this.browserWidth, aWidth) && fuzzyEquals(this.browserHeight, aHeight)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.browserWidth = aWidth;
|
||||
this.browserHeight = aHeight;
|
||||
|
|
|
@ -1630,6 +1630,10 @@ deniedTranslationOffer
|
|||
Integer count of the number of times the user opted-out offered
|
||||
page translation, either by the Not Now button or by the notification's
|
||||
close button in the "offer" state.
|
||||
autoRejectedTranlationOffer
|
||||
Integer count of the number of times the user is not offered page
|
||||
translation because they had previously clicked "Never translate this
|
||||
language" or "Never translate this site".
|
||||
showOriginalContent
|
||||
Integer count of the number of times the user activated the Show Original
|
||||
command.
|
||||
|
@ -1672,6 +1676,7 @@ Example
|
|||
"detectedLanguageChangedAfter": 2,
|
||||
"targetLanguageChanged": 0,
|
||||
"deniedTranslationOffer": 3,
|
||||
"autoRejectedTranlationOffer": 1,
|
||||
"showOriginalContent": 2,
|
||||
"translationOpportunityCountsByLanguage": {
|
||||
"fr": 100,
|
||||
|
|
|
@ -61,7 +61,6 @@ let initTable = [
|
|||
["io", "@mozilla.org/network/io-service;1", "nsIIOService2"],
|
||||
["locale", "@mozilla.org/intl/nslocaleservice;1", "nsILocaleService"],
|
||||
["logins", "@mozilla.org/login-manager;1", "nsILoginManager"],
|
||||
["netutil", "@mozilla.org/network/util;1", "nsINetUtil"],
|
||||
["obs", "@mozilla.org/observer-service;1", "nsIObserverService"],
|
||||
["perms", "@mozilla.org/permissionmanager;1", "nsIPermissionManager"],
|
||||
["prompt", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService"],
|
||||
|
|
Загрузка…
Ссылка в новой задаче