This commit is contained in:
Ryan VanderMeulen 2014-07-11 16:22:36 -04:00
Родитель f060cb5097 facb3c7516
Коммит 7fbee60fc9
50 изменённых файлов: 1080 добавлений и 301 удалений

Просмотреть файл

@ -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,162 +1,73 @@
/* 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];
// Test data as pairs of [key to press, expected content of searchbox].
const KEY_STATES = [
["d", "d"],
["i", "di"],
["v", "div"],
[".", "div."],
["VK_UP", "div.c1"],
["VK_DOWN", "div.l1"],
["VK_DOWN", "div.l1"],
["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_BACK_SPACE", "div.l1 "],
["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_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_BACK_SPACE", "div.l1 "],
["VK_BACK_SPACE", "div.l1"],
["VK_BACK_SPACE", "div.l"],
["VK_BACK_SPACE", "div."],
["VK_BACK_SPACE", "div"],
["VK_BACK_SPACE", "di"],
["VK_BACK_SPACE", "d"],
["VK_BACK_SPACE", ""],
];
// The various states of the inspector: [key, query]
// [
// what key to press,
// what should be the text in the searchbox
// ]
let keyStates = [
["d", "d"],
["i", "di"],
["v", "div"],
[".", "div."],
["VK_UP", "div.c1"],
["VK_DOWN", "div.l1"],
["VK_DOWN", "div.l1"],
["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_BACK_SPACE", "div.l1 "],
["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_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_BACK_SPACE", "div.l1 "],
["VK_BACK_SPACE", "div.l1"],
["VK_BACK_SPACE", "div.l"],
["VK_BACK_SPACE", "div."],
["VK_BACK_SPACE", "div"],
["VK_BACK_SPACE", "di"],
["VK_BACK_SPACE", "d"],
["VK_BACK_SPACE", ""],
];
const TEST_URL = TEST_URL_ROOT +
"doc_inspector_search-suggestions.html";
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.selectedBrowser.addEventListener("load", function onload() {
gBrowser.selectedBrowser.removeEventListener("load", onload, true);
waitForFocus(setupTest, content);
}, true);
let test = asyncTest(function* () {
let { inspector } = yield openInspectorForURL(TEST_URL);
yield focusSearchBoxUsingShortcut(inspector.panelWin);
content.location = "http://mochi.test:8888/browser/browser/devtools/inspector/test/doc_inspector_search-suggestions.html";
for (let [key, query] of KEY_STATES) {
info("Pressing key " + key + " to get searchbox value as " + query);
function $(id) {
if (id == null) return null;
return content.document.getElementById(id);
}
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;
is(inspector.searchBox.value, query, "The searchbox value is correct.");
}
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;
}
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,20 +173,23 @@ public class TabsPanel extends LinearLayout
mMenuButton.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
final Menu menu = mPopupMenu.getMenu();
// Each panel has a "+" shortcut button, so don't show it for that panel.
menu.findItem(R.id.new_tab).setVisible(mCurrentPanel != Panel.NORMAL_TABS);
menu.findItem(R.id.new_private_tab).setVisible(mCurrentPanel != Panel.PRIVATE_TABS);
// Only show "Clear * tabs" for current panel.
menu.findItem(R.id.close_all_tabs).setVisible(mCurrentPanel == Panel.NORMAL_TABS);
menu.findItem(R.id.close_private_tabs).setVisible(mCurrentPanel == Panel.PRIVATE_TABS);
mPopupMenu.show();
showMenu();
}
});
mPopupMenu.setAnchor(mMenuButton);
}
public void showMenu() {
final Menu menu = mPopupMenu.getMenu();
// Each panel has a "+" shortcut button, so don't show it for that panel.
menu.findItem(R.id.new_tab).setVisible(mCurrentPanel != Panel.NORMAL_TABS);
menu.findItem(R.id.new_private_tab).setVisible(mCurrentPanel != Panel.PRIVATE_TABS);
// Only show "Clear * tabs" for current panel.
menu.findItem(R.id.close_all_tabs).setVisible(mCurrentPanel == Panel.NORMAL_TABS);
menu.findItem(R.id.close_private_tabs).setVisible(mCurrentPanel == Panel.PRIVATE_TABS);
mPopupMenu.show();
}
private void addTab() {
@ -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);
mMenuButton.setVisibility(View.VISIBLE);
mMenuButton.setEnabled(true);
if (!HardwareUtils.hasMenuButton()) {
mMenuButton.setVisibility(View.VISIBLE);
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,9 +4467,11 @@ Tab.prototype = {
});
},
setBrowserSize: function(aWidth, aHeight) {
if (fuzzyEquals(this.browserWidth, aWidth) && fuzzyEquals(this.browserHeight, aHeight)) {
return;
setBrowserSize: function(aWidth, aHeight, aForce) {
if (!aForce) {
if (fuzzyEquals(this.browserWidth, aWidth) && fuzzyEquals(this.browserHeight, aHeight)) {
return;
}
}
this.browserWidth = aWidth;

Просмотреть файл

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