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); 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 // if providerOrigin is undefined, we use the last-used provider, or the
// current/default provider. The provider selection in the share panel // current/default provider. The provider selection in the share panel
// will call sharePage with an origin for us to switch to. // 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 // 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 // define at least url. If it is undefined, we're sharing the current url in
// the browser tab. // 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; gBrowser.currentURI;
if (!this.canSharePage(sharedURI)) if (!this.canSharePage(sharedURI))
return; return;
@ -628,7 +629,6 @@ SocialShare = {
// endpoints (e.g. oexchange) that do not support additional // endpoints (e.g. oexchange) that do not support additional
// socialapi functionality. One tweak is that we shoot an event // socialapi functionality. One tweak is that we shoot an event
// containing the open graph data. // containing the open graph data.
let pageData = graphData ? graphData : this.currentShare;
if (!pageData || sharedURI == gBrowser.currentURI) { if (!pageData || sharedURI == gBrowser.currentURI) {
pageData = OpenGraphBuilder.getData(gBrowser); pageData = OpenGraphBuilder.getData(gBrowser);
if (graphData) { 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; this.currentShare = pageData;
let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, pageData); let shareEndpoint = OpenGraphBuilder.generateEndpointURL(provider.shareURL, pageData);
@ -1353,10 +1357,10 @@ SocialMarks = {
return this._toolbarHelper; return this._toolbarHelper;
}, },
markLink: function(aOrigin, aUrl) { markLink: function(aOrigin, aUrl, aTarget) {
// find the button for this provider, and open it // find the button for this provider, and open it
let id = this._toolbarHelper.idFromOrigin(aOrigin); 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) { function getShortcutOrURIAndPostData(aURL, aCallback) {
let mayInheritPrincipal = false;
let postData = null; let postData = null;
let shortcutURL = null; let shortcutURL = null;
let keyword = aURL; let keyword = aURL;
@ -1928,7 +1929,8 @@ function getShortcutOrURIAndPostData(aURL, aCallback) {
if (engine) { if (engine) {
let submission = engine.getSubmission(param); let submission = engine.getSubmission(param);
postData = submission.postData; postData = submission.postData;
aCallback({ postData: submission.postData, url: submission.uri.spec }); aCallback({ postData: submission.postData, url: submission.uri.spec,
mayInheritPrincipal: mayInheritPrincipal });
return; return;
} }
@ -1936,7 +1938,8 @@ function getShortcutOrURIAndPostData(aURL, aCallback) {
PlacesUtils.getURLAndPostDataForKeyword(keyword); PlacesUtils.getURLAndPostDataForKeyword(keyword);
if (!shortcutURL) { if (!shortcutURL) {
aCallback({ postData: postData, url: aURL }); aCallback({ postData: postData, url: aURL,
mayInheritPrincipal: mayInheritPrincipal });
return; return;
} }
@ -1968,7 +1971,12 @@ function getShortcutOrURIAndPostData(aURL, aCallback) {
postData = getPostDataStream(escapedPostData, param, encodedParam, postData = getPostDataStream(escapedPostData, param, encodedParam,
"application/x-www-form-urlencoded"); "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) { if (matches) {
@ -1991,9 +1999,15 @@ function getShortcutOrURIAndPostData(aURL, aCallback) {
// the original URL. // the original URL.
postData = null; postData = null;
aCallback({ postData: postData, url: aURL }); aCallback({ postData: postData, url: aURL,
mayInheritPrincipal: mayInheritPrincipal });
} else { } 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() { init: function() {
let mm = window.messageManager; let mm = window.messageManager;
mm.addMessageListener("Link:AddFeed", this); mm.addMessageListener("Link:AddFeed", this);
mm.addMessageListener("Link:AddIcon", this); mm.addMessageListener("Link:SetIcon", this);
mm.addMessageListener("Link:AddSearch", this); mm.addMessageListener("Link:AddSearch", this);
}, },
@ -2853,8 +2867,8 @@ const DOMLinkHandler = {
FeedHandler.addFeed(link, aMsg.target); FeedHandler.addFeed(link, aMsg.target);
break; break;
case "Link:AddIcon": case "Link:SetIcon":
return this.addIcon(aMsg.target, aMsg.data.url); return this.setIcon(aMsg.target, aMsg.data.url);
break; break;
case "Link:AddSearch": case "Link:AddSearch":
@ -2863,7 +2877,7 @@ const DOMLinkHandler = {
} }
}, },
addIcon: function(aBrowser, aURL) { setIcon: function(aBrowser, aURL) {
if (gBrowser.isFailedIcon(aURL)) if (gBrowser.isFailedIcon(aURL))
return false; return false;
@ -5181,7 +5195,8 @@ function middleMousePaste(event) {
if (where != "current" || if (where != "current" ||
lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) { lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
openUILink(data.url, event, openUILink(data.url, event,
{ ignoreButton: true }); { ignoreButton: true,
disallowInheritPrincipal: !data.mayInheritPrincipal });
} }
}); });
@ -5189,26 +5204,9 @@ function middleMousePaste(event) {
} }
function stripUnsafeProtocolOnPaste(pasteData) { function stripUnsafeProtocolOnPaste(pasteData) {
// Don't allow pasting in full URIs which inherit the security context. // Don't allow pasting javascript URIs since we don't support
const URI_INHERITS_SECURITY_CONTEXT = Ci.nsIProtocolHandler.URI_INHERITS_SECURITY_CONTEXT; // LOAD_FLAGS_DISALLOW_INHERIT_OWNER for those.
return pasteData.replace(/^(?:\s*javascript:)+/i, "");
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;
} }
function handleDroppedLink(event, url, name) function handleDroppedLink(event, url, name)

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

@ -1591,22 +1591,22 @@ nsContextMenu.prototype = {
}, },
markLink: function CM_markLink(origin) { markLink: function CM_markLink(origin) {
// send link to social, if it is the page url linkURI will be null // 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() { shareLink: function CM_shareLink() {
SocialShare.sharePage(null, { url: this.linkURI.spec }); SocialShare.sharePage(null, { url: this.linkURI.spec }, this.target);
}, },
shareImage: function CM_shareImage() { 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() { 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) { 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() { savePageAs: function CM_savePageAs() {

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

@ -147,6 +147,7 @@
<method name="loadPanel"> <method name="loadPanel">
<parameter name="pageData"/> <parameter name="pageData"/>
<parameter name="target"/>
<body><![CDATA[ <body><![CDATA[
let provider = this.provider; let provider = this.provider;
let panel = this.panel; let panel = this.panel;
@ -157,7 +158,13 @@
panel.appendChild(this.content); panel.appendChild(this.content);
let URLTemplate = provider.markURL; 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); let endpoint = OpenGraphBuilder.generateEndpointURL(URLTemplate, this.pageData);
// setup listeners // setup listeners
@ -248,6 +255,7 @@
<method name="markLink"> <method name="markLink">
<parameter name="aUrl"/> <parameter name="aUrl"/>
<parameter name="aTarget"/>
<body><![CDATA[ <body><![CDATA[
if (!aUrl) { if (!aUrl) {
this.markCurrentPage(true); this.markCurrentPage(true);
@ -259,7 +267,7 @@
// link, etc. inside the page. We also "update" the iframe to the // link, etc. inside the page. We also "update" the iframe to the
// previous url when it is closed. // previous url when it is closed.
this.content.setAttribute("src", "about:blank"); this.content.setAttribute("src", "about:blank");
this.loadPanel({ url: aUrl }); this.loadPanel({ url: aUrl }, aTarget);
this.openPanel(true); this.openPanel(true);
]]></body> ]]></body>
</method> </method>

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

@ -53,6 +53,7 @@ support-files =
file_bug970276_favicon1.ico file_bug970276_favicon1.ico
file_bug970276_favicon2.ico file_bug970276_favicon2.ico
file_dom_notifications.html file_dom_notifications.html
file_favicon_change.html
file_fullscreen-window-open.html file_fullscreen-window-open.html
get_user_media.html get_user_media.html
head.js head.js
@ -290,6 +291,7 @@ skip-if = e10s # Bug 918663 - DOMLinkAdded events don't make their way to chrome
[browser_duplicateIDs.js] [browser_duplicateIDs.js]
[browser_drag.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. 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] [browser_findbarClose.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (tries to grab an iframe directly from content) skip-if = e10s # Bug ?????? - test directly manipulates content (tries to grab an iframe directly from content)
[browser_fullscreen-window-open.js] [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) skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405)
[browser_locationBarCommand.js] [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) 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] [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") 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] [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]; return dataLines[dataLines.length-1];
} }
function keywordResult(aURL, aPostData) { function keywordResult(aURL, aPostData, aIsUnsafe) {
this.url = aURL; this.url = aURL;
this.postData = aPostData; this.postData = aPostData;
this.isUnsafe = aIsUnsafe;
} }
function keyWordData() {} function keyWordData() {}
@ -52,20 +53,20 @@ var testData = [
new keywordResult("http://bmget-nosearch/", null)], new keywordResult("http://bmget-nosearch/", null)],
[new searchKeywordData("searchget", "http://searchget/?search={searchTerms}", null, "foo4"), [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 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 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 // Bookmark keywords that don't take parameters should not be activated if a
// parameter is passed (bug 420328). // parameter is passed (bug 420328).
[new bmKeywordData("bmget-noparam", "http://bmget-noparam/", null, "foo7"), [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 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) // Test escaping (%s = escaped, %S = raw)
// UTF-8 default // UTF-8 default
@ -87,7 +88,7 @@ var testData = [
// getShortcutOrURIAndPostData for non-keywords (setupKeywords only adds keywords for // getShortcutOrURIAndPostData for non-keywords (setupKeywords only adds keywords for
// bmKeywordData objects) // bmKeywordData objects)
[{keyword: "http://gavinsharp.com"}, [{keyword: "http://gavinsharp.com"},
new keywordResult(null, null)] new keywordResult(null, null, true)]
]; ];
function test() { function test() {
@ -108,6 +109,7 @@ function test() {
let expected = result.url || query; let expected = result.url || query;
is(returnedData.url, expected, "got correct URL for " + data.keyword); is(returnedData.url, expected, "got correct URL for " + data.keyword);
is(getPostDataString(returnedData.postData), result.postData, "got correct postData 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(); cleanupKeywords();
}).then(finish); }).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:", ""],
["javascript:1+1", "1+1"], ["javascript:1+1", "1+1"],
["javascript:document.domain", "document.domain"], ["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: // Nested things get confusing because some things don't parse as URIs:
["javascript:javascript:alert('hi!')", "alert('hi!')"], ["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:javascript:alert('hi!')", "data:javascript:alert('hi!')"],
["javascript: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!')", "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); 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/shortlink_linkrel.html
opengraph/shorturl_link.html opengraph/shorturl_link.html
opengraph/shorturl_linkrel.html opengraph/shorturl_linkrel.html
microdata.html
share.html share.html
social_activate.html social_activate.html
social_activate_iframe.html social_activate_iframe.html

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

@ -170,5 +170,76 @@ var tests = {
} }
port.postMessage({topic: "test-init"}); port.postMessage({topic: "test-init"});
executeSoon(runOneTest); 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) { testButtonOnDisable: function(next) {
// enable the provider now // enable the provider now
let provider = Social._getProviderFromOrigin(manifest2.origin); 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); shareData = JSON.parse(e.detail);
updateTextNode(document.getElementById("shared"), shareData.url); updateTextNode(document.getElementById("shared"), shareData.url);
socialMarkUpdate(true); socialMarkUpdate(true);
var port = navigator.mozSocial.getWorker().port;
port.postMessage({topic: "share-data-message", result: shareData});
}); });
</script> </script>
</head> </head>

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

@ -265,6 +265,7 @@
return; // Do nothing for right clicks return; // Do nothing for right clicks
var url = this.value; var url = this.value;
var mayInheritPrincipal = false;
var postData = null; var postData = null;
var action = this._parseActionUrl(url); var action = this._parseActionUrl(url);
@ -287,7 +288,7 @@
} }
else { else {
this._canonizeURL(aTriggeringEvent, response => { this._canonizeURL(aTriggeringEvent, response => {
[url, postData] = response; [url, postData, mayInheritPrincipal] = response;
if (url) { if (url) {
matchLastLocationChange = (lastLocationChange == matchLastLocationChange = (lastLocationChange ==
gBrowser.selectedBrowser.lastLocationChange); gBrowser.selectedBrowser.lastLocationChange);
@ -309,10 +310,11 @@
} }
function loadCurrent() { function loadCurrent() {
let webnav = Ci.nsIWebNavigation; openUILinkIn(url, "current", {
let flags = webnav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | allowThirdPartyFixup: true,
webnav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; disallowInheritPrincipal: !mayInheritPrincipal,
gBrowser.loadURIWithFlags(url, flags, null, null, postData); postData: postData
});
} }
// Focus the content area before triggering loads, since if the load // Focus the content area before triggering loads, since if the load
@ -422,7 +424,7 @@
} }
getShortcutOrURIAndPostData(url, data => { getShortcutOrURIAndPostData(url, data => {
aCallback([data.url, data.postData]); aCallback([data.url, data.postData, data.mayInheritPrincipal]);
}); });
]]></body> ]]></body>
</method> </method>

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

@ -281,11 +281,18 @@ function openLinkIn(url, where, params) {
getBoolPref("browser.tabs.loadInBackground"); 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) { if (where == "current" && w.gBrowser.selectedTab.pinned) {
try { try {
let uriObj = Services.io.newURI(url, null, null); // nsIURI.host can throw for non-nsStandardURL nsIURIs.
if (!uriObj.schemeIs("javascript") && if (!uriObj || (!uriObj.schemeIs("javascript") &&
w.gBrowser.currentURI.host != uriObj.host) { w.gBrowser.currentURI.host != uriObj.host)) {
where = "tab"; where = "tab";
loadInBackground = false; loadInBackground = false;
} }
@ -302,12 +309,19 @@ function openLinkIn(url, where, params) {
switch (where) { switch (where) {
case "current": case "current":
let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
if (aAllowThirdPartyFixup) { if (aAllowThirdPartyFixup) {
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS; 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; flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
w.gBrowser.loadURIWithFlags(url, flags, aReferrerURI, null, aPostData); w.gBrowser.loadURIWithFlags(url, flags, aReferrerURI, null, aPostData);
break; break;
case "tabshifted": case "tabshifted":

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

@ -467,7 +467,6 @@ var BookmarkPropertiesPanel = {
// The order here is important! We have to uninit the panel first, otherwise // The order here is important! We have to uninit the panel first, otherwise
// late changes could force it to commit more transactions. // late changes could force it to commit more transactions.
gEditItemOverlay.uninitPanel(true); gEditItemOverlay.uninitPanel(true);
gEditItemOverlay = null;
this._endBatch(); this._endBatch();
window.arguments[0].performed = true; window.arguments[0].performed = true;
}, },
@ -477,7 +476,6 @@ var BookmarkPropertiesPanel = {
// changes done as part of Undo may change the panel contents and by // changes done as part of Undo may change the panel contents and by
// that force it to commit more transactions. // that force it to commit more transactions.
gEditItemOverlay.uninitPanel(true); gEditItemOverlay.uninitPanel(true);
gEditItemOverlay = null;
this._endBatch(); this._endBatch();
PlacesUtils.transactionManager.undoTransaction(); PlacesUtils.transactionManager.undoTransaction();
window.arguments[0].performed = false; window.arguments[0].performed = false;

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

@ -209,6 +209,12 @@ var gEditItemOverlay = {
// observe only tags changes, through bookmarks. // observe only tags changes, through bookmarks.
if (this._itemId != -1 || this._uri || this._multiEdit) if (this._itemId != -1 || this._uri || this._multiEdit)
PlacesUtils.bookmarks.addObserver(this, false); 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); window.addEventListener("unload", this, false);
this._observersAdded = true; this._observersAdded = true;
} }
@ -388,6 +394,12 @@ var gEditItemOverlay = {
if (this._itemId != -1 || this._uri || this._multiEdit) if (this._itemId != -1 || this._uri || this._multiEdit)
PlacesUtils.bookmarks.removeObserver(this); 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; this._observersAdded = false;
} }
@ -528,7 +540,7 @@ var gEditItemOverlay = {
return false; return false;
}, },
onNamePickerChange: function EIO_onNamePickerChange() { onNamePickerBlur: function EIO_onNamePickerBlur() {
if (this._itemId == -1) if (this._itemId == -1)
return; return;
@ -879,6 +891,11 @@ var gEditItemOverlay = {
this._element("tagsField").value = tags.join(", "); this._element("tagsField").value = tags.join(", ");
this._updateTags(); this._updateTags();
break; 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": case "unload":
this.uninitPanel(false); this.uninitPanel(false);
break; break;

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

@ -33,7 +33,6 @@
control="editBMPanel_namePicker" control="editBMPanel_namePicker"
observes="paneElementsBroadcaster"/> observes="paneElementsBroadcaster"/>
<textbox id="editBMPanel_namePicker" <textbox id="editBMPanel_namePicker"
onblur="gEditItemOverlay.onNamePickerChange();"
observes="paneElementsBroadcaster"/> observes="paneElementsBroadcaster"/>
</row> </row>
@ -45,7 +44,6 @@
observes="paneElementsBroadcaster"/> observes="paneElementsBroadcaster"/>
<textbox id="editBMPanel_locationField" <textbox id="editBMPanel_locationField"
class="uri-element" class="uri-element"
onblur="gEditItemOverlay.onLocationFieldBlur();"
observes="paneElementsBroadcaster"/> observes="paneElementsBroadcaster"/>
</row> </row>
@ -150,7 +148,6 @@
completedefaultindex="true" completedefaultindex="true"
tabscrolling="true" tabscrolling="true"
showcommentcolumn="true" showcommentcolumn="true"
onblur="gEditItemOverlay.onTagsFieldBlur();"
observes="paneElementsBroadcaster" observes="paneElementsBroadcaster"
placeholder="&editBookmarkOverlay.tagsEmptyDesc.label;"/> placeholder="&editBookmarkOverlay.tagsEmptyDesc.label;"/>
<button id="editBMPanel_tagsSelectorExpander" <button id="editBMPanel_tagsSelectorExpander"
@ -180,7 +177,6 @@
control="editBMPanel_keywordField" control="editBMPanel_keywordField"
observes="paneElementsBroadcaster"/> observes="paneElementsBroadcaster"/>
<textbox id="editBMPanel_keywordField" <textbox id="editBMPanel_keywordField"
onblur="gEditItemOverlay.onKeywordFieldBlur();"
observes="paneElementsBroadcaster"/> observes="paneElementsBroadcaster"/>
</row> </row>
@ -193,7 +189,6 @@
observes="paneElementsBroadcaster"/> observes="paneElementsBroadcaster"/>
<textbox id="editBMPanel_descriptionField" <textbox id="editBMPanel_descriptionField"
multiline="true" multiline="true"
onblur="gEditItemOverlay.onDescriptionFieldBlur();"
observes="paneElementsBroadcaster"/> observes="paneElementsBroadcaster"/>
</row> </row>
</rows> </rows>

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

@ -127,7 +127,7 @@ gTests.push({
PlacesUtils.bookmarks.getItemTitle(PlacesUtils.unfiledBookmarksFolderId), PlacesUtils.bookmarks.getItemTitle(PlacesUtils.unfiledBookmarksFolderId),
"Node title is correct"); "Node title is correct");
// Blur the field and ensure root's name has not been changed. // Blur the field and ensure root's name has not been changed.
this.window.gEditItemOverlay.onNamePickerChange(); this.window.gEditItemOverlay.onNamePickerBlur();
is(namepicker.value, is(namepicker.value,
PlacesUtils.bookmarks.getItemTitle(PlacesUtils.unfiledBookmarksFolderId), PlacesUtils.bookmarks.getItemTitle(PlacesUtils.unfiledBookmarksFolderId),
"Root title is correct"); "Root title is correct");

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

@ -1693,10 +1693,8 @@ var gApplicationsPane = {
var typeItem = this._list.selectedItem; var typeItem = this._list.selectedItem;
var handlerInfo = this._handledTypes[typeItem.type]; var handlerInfo = this._handledTypes[typeItem.type];
openDialog("chrome://browser/content/preferences/applicationManager.xul", gSubDialog.open("chrome://browser/content/preferences/applicationManager.xul",
"", "resizable=no", handlerInfo);
"modal,centerscreen,resizable=no",
handlerInfo);
// Rebuild the actions menu so that we revert to the previous selection, // Rebuild the actions menu so that we revert to the previous selection,
// or "Always ask" if the previous default application has been removed // or "Always ask" if the previous default application has been removed
@ -1757,9 +1755,8 @@ var gApplicationsPane = {
params.filename = null; params.filename = null;
params.handlerApp = null; params.handlerApp = null;
window.openDialog("chrome://global/content/appPicker.xul", null, gSubDialog.open("chrome://global/content/appPicker.xul",
"chrome,modal,centerscreen,titlebar,dialog=yes", null, params);
params);
if (this.isValidHandlerApp(params.handlerApp)) { if (this.isValidHandlerApp(params.handlerApp)) {
handlerApp = params.handlerApp; handlerApp = params.handlerApp;

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

@ -222,12 +222,19 @@ TranslationUI.prototype = {
// Check if we should never show the infobar for this language. // Check if we should never show the infobar for this language.
let neverForLangs = let neverForLangs =
Services.prefs.getCharPref("browser.translation.neverForLanguages"); Services.prefs.getCharPref("browser.translation.neverForLanguages");
if (neverForLangs.split(",").indexOf(this.detectedLanguage) != -1) if (neverForLangs.split(",").indexOf(this.detectedLanguage) != -1) {
TranslationHealthReport.recordAutoRejectedTranslationOffer();
return false; return false;
}
// or if we should never show the infobar for this domain. // or if we should never show the infobar for this domain.
let perms = Services.perms; 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) { showTranslationUI: function(aDetectedLanguage) {
@ -298,6 +305,20 @@ let TranslationHealthReport = {
this._withProvider(provider => provider.recordMissedTranslationOpportunity(language)); 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. * Record a translation in the health report.
* @param langFrom * @param langFrom
@ -413,6 +434,7 @@ TranslationMeasurement1.prototype = Object.freeze({
showOriginalContent: DAILY_COUNTER_FIELD, showOriginalContent: DAILY_COUNTER_FIELD,
detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD, detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD,
showTranslationUI: DAILY_LAST_NUMERIC_FIELD, showTranslationUI: DAILY_LAST_NUMERIC_FIELD,
autoRejectedTranslationOffer: DAILY_COUNTER_FIELD,
}, },
shouldIncludeField: function (field) { shouldIncludeField: function (field) {
@ -511,6 +533,15 @@ TranslationProvider.prototype = Object.freeze({
}.bind(this)); }.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()) { recordTranslation: function (langFrom, langTo, numCharacters, date=new Date()) {
let m = this.getMeasurement(TranslationMeasurement1.prototype.name, let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
TranslationMeasurement1.prototype.version); TranslationMeasurement1.prototype.version);

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

@ -36,7 +36,8 @@ let MetricsChecker = {
showOriginal: day.get("showOriginalContent") || 0, showOriginal: day.get("showOriginalContent") || 0,
detectedLanguageChangedBefore: day.get("detectedLanguageChangedBefore") || 0, detectedLanguageChangedBefore: day.get("detectedLanguageChangedBefore") || 0,
detectedLanguageChangeAfter: day.get("detectedLanguageChangedAfter") || 0, detectedLanguageChangeAfter: day.get("detectedLanguageChangedAfter") || 0,
targetLanguageChanged: day.get("targetLanguageChanged") || 0 targetLanguageChanged: day.get("targetLanguageChanged") || 0,
autoRejectedOffers: day.get("autoRejectedTranslationOffer") || 0
}; };
this._metricsTime = metricsTime; 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) { function getInfobarElement(browser, anonid) {
let notif = browser.translationUI let notif = browser.translationUI
.notificationBox.getNotificationWithValue("translation"); .notificationBox.getNotificationWithValue("translation");

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

@ -232,6 +232,11 @@ add_task(function* test_show_original() {
yield test_simple_counter("recordShowOriginalContent", "showOriginalContent"); yield test_simple_counter("recordShowOriginalContent", "showOriginalContent");
}); });
add_task(function* test_show_original() {
yield test_simple_counter("recordAutoRejectedTranslationOffer",
"autoRejectedTranslationOffer");
});
add_task(function* test_collect_daily() { add_task(function* test_collect_daily() {
let storage = yield Metrics.Storage("translation"); let storage = yield Metrics.Storage("translation");
let provider = new TranslationProvider(); let provider = new TranslationProvider();
@ -291,6 +296,8 @@ add_task(function* test_healthreporter_json() {
yield provider.recordDeniedTranslationOffer(); yield provider.recordDeniedTranslationOffer();
yield provider.recordAutoRejectedTranslationOffer();
yield provider.recordShowOriginalContent(); yield provider.recordShowOriginalContent();
yield reporter.collectMeasurements(); yield reporter.collectMeasurements();
@ -329,6 +336,9 @@ add_task(function* test_healthreporter_json() {
Assert.ok("deniedTranslationOffer" in translations); Assert.ok("deniedTranslationOffer" in translations);
Assert.equal(translations["deniedTranslationOffer"], 1); Assert.equal(translations["deniedTranslationOffer"], 1);
Assert.ok("autoRejectedTranslationOffer" in translations);
Assert.equal(translations["autoRejectedTranslationOffer"], 1);
Assert.ok("showOriginalContent" in translations); Assert.ok("showOriginalContent" in translations);
Assert.equal(translations["showOriginalContent"], 1); Assert.equal(translations["showOriginalContent"], 1);
} finally { } finally {
@ -357,6 +367,8 @@ add_task(function* test_healthreporter_json2() {
yield provider.recordDeniedTranslationOffer(); yield provider.recordDeniedTranslationOffer();
yield provider.recordAutoRejectedTranslationOffer();
yield provider.recordShowOriginalContent(); yield provider.recordShowOriginalContent();
yield reporter.collectMeasurements(); yield reporter.collectMeasurements();
@ -378,6 +390,7 @@ add_task(function* test_healthreporter_json2() {
Assert.ok(!("detectedLanguageChangedBefore" in translations)); Assert.ok(!("detectedLanguageChangedBefore" in translations));
Assert.ok(!("detectedLanguageChangedAfter" in translations)); Assert.ok(!("detectedLanguageChangedAfter" in translations));
Assert.ok(!("deniedTranslationOffer" in translations)); Assert.ok(!("deniedTranslationOffer" in translations));
Assert.ok(!("autoRejectedTranslationOffer" in translations));
Assert.ok(!("showOriginalContent" in translations)); Assert.ok(!("showOriginalContent" in translations));
} finally { } finally {
reporter._shutdown(); reporter._shutdown();

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

@ -5,7 +5,7 @@
"use strict"; "use strict";
const promise = require("devtools/toolkit/deprecated-sync-thenables"); 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); loader.lazyGetter(this, "AutocompletePopup", () => require("devtools/shared/autocomplete-popup").AutocompletePopup);
// Maximum number of selector suggestions shown in the panel. // 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. * 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 * @constructor
* @param InspectorPanel aInspector * @param InspectorPanel aInspector
* The InspectorPanel whose `walker` attribute should be used for * 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 // For testing, we need to be able to wait for the most recent node request
// to finish. Tests can watch this promise for that. // to finish. Tests can watch this promise for that.
this._lastQuery = promise.resolve(null); this._lastQuery = promise.resolve(null);
EventEmitter.decorate(this);
} }
exports.SelectorSearch = SelectorSearch; exports.SelectorSearch = SelectorSearch;
@ -183,6 +188,7 @@ SelectorSearch.prototype = {
_onHTMLSearch: function() { _onHTMLSearch: function() {
let query = this.searchBox.value; let query = this.searchBox.value;
if (query == this._lastSearched) { if (query == this._lastSearched) {
this.emit("processing-done");
return; return;
} }
this._lastSearched = query; this._lastSearched = query;
@ -196,6 +202,7 @@ SelectorSearch.prototype = {
if (this.searchPopup.isOpen) { if (this.searchPopup.isOpen) {
this.searchPopup.hidePopup(); this.searchPopup.hidePopup();
} }
this.emit("processing-done");
return; return;
} }
@ -258,7 +265,7 @@ SelectorSearch.prototype = {
} }
this.searchBox.classList.add("devtools-no-search-result"); this.searchBox.classList.add("devtools-no-search-result");
return this.showSuggestions(); return this.showSuggestions();
}); }).then(() => this.emit("processing-done"));
}, },
/** /**
@ -332,7 +339,10 @@ SelectorSearch.prototype = {
aEvent.preventDefault(); aEvent.preventDefault();
aEvent.stopPropagation(); aEvent.stopPropagation();
if (this._searchResults && this._searchResults.length > 0) { 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(); this._onHTMLSearch();
break; break;
} }
this.emit("processing-done");
}, },
/** /**

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

@ -48,8 +48,7 @@ skip-if = true # Bug 1028609
[browser_inspector_search-02.js] [browser_inspector_search-02.js]
[browser_inspector_search-03.js] [browser_inspector_search-03.js]
[browser_inspector_select-last-selected.js] [browser_inspector_select-last-selected.js]
# [browser_inspector_search-navigation.js] [browser_inspector_search-navigation.js]
# Disabled for too many intermittent failures (bug 851349)
[browser_inspector_sidebarstate.js] [browser_inspector_sidebarstate.js]
[browser_inspector_switch-to-inspector-on-pick.js] [browser_inspector_switch-to-inspector-on-pick.js]
[browser_inspector_update-on-navigation.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. /* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */ http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function test() // Check that searchbox value is correct when suggestions popup is navigated
{ // with keyboard.
requestLongerTimeout(2);
let inspector, searchBox, state, panel; // Test data as pairs of [key to press, expected content of searchbox].
let panelOpeningStates = [0, 3, 9, 14, 17]; const KEY_STATES = [
let panelClosingStates = [2, 8, 13, 16]; ["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] const TEST_URL = TEST_URL_ROOT +
// [ "doc_inspector_search-suggestions.html";
// 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", ""],
];
gBrowser.selectedTab = gBrowser.addTab(); let test = asyncTest(function* () {
gBrowser.selectedBrowser.addEventListener("load", function onload() { let { inspector } = yield openInspectorForURL(TEST_URL);
gBrowser.selectedBrowser.removeEventListener("load", onload, true); yield focusSearchBoxUsingShortcut(inspector.panelWin);
waitForFocus(setupTest, content);
}, true);
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) { let done = inspector.searchSuggestions.once("processing-done");
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);
EventUtils.synthesizeKey(key, {}, inspector.panelWin); 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 = { this.ContentLinkHandler = {
init: function(chromeGlobal) { init: function(chromeGlobal) {
chromeGlobal.addEventListener("DOMLinkAdded", (event) => { chromeGlobal.addEventListener("DOMLinkAdded", (event) => {
this.onLinkAdded(event, chromeGlobal); this.onLinkEvent(event, chromeGlobal);
}, false);
chromeGlobal.addEventListener("DOMLinkChanged", (event) => {
this.onLinkEvent(event, chromeGlobal);
}, false); }, false);
}, },
onLinkAdded: function(event, chromeGlobal) { onLinkEvent: function(event, chromeGlobal) {
var link = event.originalTarget; var link = event.originalTarget;
var rel = link.rel && link.rel.toLowerCase(); var rel = link.rel && link.rel.toLowerCase();
if (!link || !link.ownerDocument || !rel || !link.href) if (!link || !link.ownerDocument || !rel || !link.href)
@ -47,7 +50,7 @@ this.ContentLinkHandler = {
switch (relVal) { switch (relVal) {
case "feed": case "feed":
case "alternate": case "alternate":
if (!feedAdded) { if (!feedAdded && event.type == "DOMLinkAdded") {
if (!rels.feed && rels.alternate && rels.stylesheet) if (!rels.feed && rels.alternate && rels.stylesheet)
break; break;
@ -69,11 +72,11 @@ this.ContentLinkHandler = {
if (!uri) if (!uri)
break; break;
[iconAdded] = chromeGlobal.sendSyncMessage("Link:AddIcon", {url: uri.spec}); [iconAdded] = chromeGlobal.sendSyncMessage("Link:SetIcon", {url: uri.spec});
} }
break; break;
case "search": case "search":
if (!searchAdded) { if (!searchAdded && event.type == "DOMLinkAdded") {
var type = link.type && link.type.toLowerCase(); var type = link.type && link.type.toLowerCase();
type = type.replace(/^\s+|\s*(?:;.*)?$/g, ""); type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");

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

@ -498,7 +498,7 @@ this.OpenGraphBuilder = {
return endpointURL; return endpointURL;
}, },
getData: function(browser) { getData: function(browser, target) {
let res = { let res = {
url: this._validateURL(browser, browser.currentURI.spec), url: this._validateURL(browser, browser.currentURI.spec),
title: browser.contentDocument.title, title: browser.contentDocument.title,
@ -507,9 +507,14 @@ this.OpenGraphBuilder = {
this._getMetaData(browser, res); this._getMetaData(browser, res);
this._getLinkData(browser, res); this._getLinkData(browser, res);
this._getPageData(browser, res); this._getPageData(browser, res);
res.microdata = this.getMicrodata(browser, target);
return res; return res;
}, },
getMicrodata: function (browser, target) {
return getMicrodata(browser.contentDocument, target);
},
_getMetaData: function(browser, o) { _getMetaData: function(browser, o) {
// query for standardized meta data // query for standardized meta data
let els = browser.contentDocument let els = browser.contentDocument
@ -522,7 +527,14 @@ this.OpenGraphBuilder = {
if (!value) if (!value)
continue; continue;
value = unescapeService.unescape(value.trim()); 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 "title":
case "og:title": case "og:title":
o.title = value; o.title = value;
@ -577,6 +589,19 @@ this.OpenGraphBuilder = {
case "image_src": case "image_src":
o.previews.push(url); o.previews.push(url);
break; 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; 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 \ RobocopShare2.java \
RobocopUtils.java \ RobocopUtils.java \
PaintedSurface.java \ PaintedSurface.java \
StructuredLogger.java \
$(NULL) $(NULL)
java-harness := $(addprefix $(srcdir)/,$(_JAVA_HARNESS)) 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 "nsMathUtils.h"
#include "nsTArrayForwardDeclare.h" #include "nsTArrayForwardDeclare.h"
#include "Units.h" #include "Units.h"
#include "mozilla/dom/AutocompleteInfoBinding.h"
#if defined(XP_WIN) #if defined(XP_WIN)
// Undefine LoadImage to prevent naming conflict with Windows. // Undefine LoadImage to prevent naming conflict with Windows.
@ -2045,8 +2046,22 @@ public:
* *
* @return whether aAttr was valid and can be cached. * @return whether aAttr was valid and can be cached.
*/ */
static AutocompleteAttrState SerializeAutocompleteAttribute(const nsAttrValue* aAttr, static AutocompleteAttrState
nsAString& aResult); 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 * This will parse aSource, to extract the value of the pseudo attribute
@ -2205,8 +2220,9 @@ private:
static void* AllocClassMatchingInfo(nsINode* aRootNode, static void* AllocClassMatchingInfo(nsINode* aRootNode,
const nsString* aClasses); const nsString* aClasses);
// Fills in aInfo with the tokens from the supplied autocomplete attribute.
static AutocompleteAttrState InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal, static AutocompleteAttrState InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
nsAString& aResult); mozilla::dom::AutocompleteInfo& aInfo);
static nsIXPConnect *sXPConnect; static nsIXPConnect *sXPConnect;

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

@ -766,17 +766,73 @@ nsContentUtils::IsAutocompleteEnabled(nsIDOMHTMLInputElement* aInput)
nsContentUtils::AutocompleteAttrState nsContentUtils::AutocompleteAttrState
nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr, nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
nsAString& aResult) nsAString& aResult,
AutocompleteAttrState aCachedState)
{ {
AutocompleteAttrState state = InternalSerializeAutocompleteAttribute(aAttr, aResult); if (!aAttr ||
if (state == eAutocompleteAttrState_Valid) { aCachedState == nsContentUtils::eAutocompleteAttrState_Invalid) {
ASCIIToLower(aResult); return aCachedState;
} else {
aResult.Truncate();
} }
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; 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. * Helper to validate the @autocomplete tokens.
* *
@ -784,7 +840,7 @@ nsContentUtils::SerializeAutocompleteAttribute(const nsAttrValue* aAttr,
*/ */
nsContentUtils::AutocompleteAttrState nsContentUtils::AutocompleteAttrState
nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal, nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
nsAString& aResult) mozilla::dom::AutocompleteInfo& aInfo)
{ {
// No sandbox attribute so we are done // No sandbox attribute so we are done
if (!aAttrVal) { if (!aAttrVal) {
@ -801,6 +857,7 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
AutocompleteCategory category; AutocompleteCategory category;
nsAttrValue enumValue; nsAttrValue enumValue;
nsAutoString str;
bool result = enumValue.ParseEnumValue(tokenString, kAutocompleteFieldNameTable, false); bool result = enumValue.ParseEnumValue(tokenString, kAutocompleteFieldNameTable, false);
if (result) { if (result) {
// Off/Automatic/Normal categories. // Off/Automatic/Normal categories.
@ -809,7 +866,9 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
if (numTokens > 1) { if (numTokens > 1) {
return eAutocompleteAttrState_Invalid; return eAutocompleteAttrState_Invalid;
} }
enumValue.ToString(aResult); enumValue.ToString(str);
ASCIIToLower(str);
aInfo.mFieldName.Assign(str);
return eAutocompleteAttrState_Valid; return eAutocompleteAttrState_Valid;
} }
@ -837,7 +896,9 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
category = eAutocompleteCategory_CONTACT; category = eAutocompleteCategory_CONTACT;
} }
enumValue.ToString(aResult); enumValue.ToString(str);
ASCIIToLower(str);
aInfo.mFieldName.Assign(str);
// We are done if this was the only token. // We are done if this was the only token.
if (numTokens == 1) { if (numTokens == 1) {
@ -851,10 +912,10 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
nsAttrValue contactFieldHint; nsAttrValue contactFieldHint;
result = contactFieldHint.ParseEnumValue(tokenString, kAutocompleteContactFieldHintTable, false); result = contactFieldHint.ParseEnumValue(tokenString, kAutocompleteContactFieldHintTable, false);
if (result) { if (result) {
aResult.Insert(' ', 0);
nsAutoString contactFieldHintString; nsAutoString contactFieldHintString;
contactFieldHint.ToString(contactFieldHintString); contactFieldHint.ToString(contactFieldHintString);
aResult.Insert(contactFieldHintString, 0); ASCIIToLower(contactFieldHintString);
aInfo.mContactType.Assign(contactFieldHintString);
if (index == 0) { if (index == 0) {
return eAutocompleteAttrState_Valid; return eAutocompleteAttrState_Valid;
} }
@ -866,16 +927,21 @@ nsContentUtils::InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrV
// Check for billing/shipping tokens // Check for billing/shipping tokens
nsAttrValue fieldHint; nsAttrValue fieldHint;
if (fieldHint.ParseEnumValue(tokenString, kAutocompleteFieldHintTable, false)) { if (fieldHint.ParseEnumValue(tokenString, kAutocompleteFieldHintTable, false)) {
aResult.Insert(' ', 0);
nsString fieldHintString; nsString fieldHintString;
fieldHint.ToString(fieldHintString); fieldHint.ToString(fieldHintString);
aResult.Insert(fieldHintString, 0); ASCIIToLower(fieldHintString);
aInfo.mAddressType.Assign(fieldHintString);
if (index == 0) { if (index == 0) {
return eAutocompleteAttrState_Valid; return eAutocompleteAttrState_Valid;
} }
--index; --index;
} }
// Clear the fields as the autocomplete attribute is invalid.
aInfo.mAddressType.Truncate();
aInfo.mContactType.Truncate();
aInfo.mFieldName.Truncate();
return eAutocompleteAttrState_Invalid; return eAutocompleteAttrState_Invalid;
} }

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

@ -1529,23 +1529,10 @@ HTMLInputElement::GetAutocomplete(nsAString& aValue)
{ {
aValue.Truncate(0); aValue.Truncate(0);
const nsAttrValue* attributeVal = GetParsedAttr(nsGkAtoms::autocomplete); 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; return NS_OK;
} }
@ -1555,6 +1542,15 @@ HTMLInputElement::SetAutocomplete(const nsAString& aValue)
return SetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete, nullptr, aValue, true); 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 int32_t
HTMLInputElement::TabIndexDefault() HTMLInputElement::TabIndexDefault()
{ {

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

@ -370,6 +370,8 @@ public:
SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv); SetHTMLAttr(nsGkAtoms::autocomplete, aValue, aRv);
} }
void GetAutocompleteInfo(AutocompleteInfo& aInfo);
bool Autofocus() const bool Autofocus() const
{ {
return GetBoolAttr(nsGkAtoms::autofocus); return GetBoolAttr(nsGkAtoms::autofocus);

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

@ -320,6 +320,7 @@ HTMLLinkElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
// to get updated information about the visitedness from Link. // to get updated information about the visitedness from Link.
if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) { if (aName == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
Link::ResetLinkState(!!aNotify, true); Link::ResetLinkState(!!aNotify, true);
CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
} }
if (NS_SUCCEEDED(rv) && aNameSpaceID == kNameSpaceID_None && 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. // to get updated information about the visitedness from Link.
if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) { if (aAttribute == nsGkAtoms::href && kNameSpaceID_None == aNameSpaceID) {
Link::ResetLinkState(!!aNotify, false); Link::ResetLinkState(!!aNotify, false);
CreateAndDispatchEvent(OwnerDoc(), NS_LITERAL_STRING("DOMLinkChanged"));
} }
return rv; 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_MANIFESTS += ['forms/mochitest.ini', 'mochitest.ini']
MOCHITEST_CHROME_MANIFESTS += ['chrome.ini'] MOCHITEST_CHROME_MANIFESTS += ['chrome.ini', 'forms/chrome.ini']
BROWSER_CHROME_MANIFESTS += ['browser.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; readonly attribute HTMLInputElement? ownerNumberControl;
boolean mozIsTextField(boolean aExcludePassword); boolean mozIsTextField(boolean aExcludePassword);
[ChromeOnly]
AutocompleteInfo getAutocompleteInfo();
}; };
partial interface HTMLInputElement { partial interface HTMLInputElement {

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

@ -39,6 +39,7 @@ WEBIDL_FILES = [
'AudioStreamTrack.webidl', 'AudioStreamTrack.webidl',
'AudioTrack.webidl', 'AudioTrack.webidl',
'AudioTrackList.webidl', 'AudioTrackList.webidl',
'AutocompleteInfo.webidl',
'BarProp.webidl', 'BarProp.webidl',
'BatteryManager.webidl', 'BatteryManager.webidl',
'BeforeUnloadEvent.webidl', 'BeforeUnloadEvent.webidl',

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

@ -2299,8 +2299,10 @@ public class BrowserApp extends GeckoApp
@Override @Override
public void openOptionsMenu() { public void openOptionsMenu() {
if (!hasTabsSideBar() && areTabsShown()) if (areTabsShown()) {
mTabsPanel.showMenu();
return; return;
}
// Scroll custom menu to the top // Scroll custom menu to the top
if (mMenuPanel != null) if (mMenuPanel != null)

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

@ -24,6 +24,7 @@
android:padding="@dimen/browser_toolbar_button_padding" android:padding="@dimen/browser_toolbar_button_padding"
android:src="@drawable/menu_tabs" android:src="@drawable/menu_tabs"
android:contentDescription="@string/menu" android:contentDescription="@string/menu"
android:background="@drawable/action_bar_button"/> android:background="@drawable/action_bar_button"
android:visibility="gone"/>
</merge> </merge>

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

@ -32,6 +32,7 @@
android:padding="@dimen/browser_toolbar_button_padding" android:padding="@dimen/browser_toolbar_button_padding"
android:src="@drawable/menu_tabs" android:src="@drawable/menu_tabs"
android:contentDescription="@string/menu" android:contentDescription="@string/menu"
android:background="@drawable/action_bar_button"/> android:background="@drawable/action_bar_button"
android:visibility="gone"/>
</merge> </merge>

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

@ -17,6 +17,7 @@ import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract; import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.animation.PropertyAnimator; import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.animation.ViewHelper; import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.widget.GeckoPopupMenu; import org.mozilla.gecko.widget.GeckoPopupMenu;
import org.mozilla.gecko.widget.IconTabWidget; import org.mozilla.gecko.widget.IconTabWidget;
@ -172,20 +173,23 @@ public class TabsPanel extends LinearLayout
mMenuButton.setOnClickListener(new Button.OnClickListener() { mMenuButton.setOnClickListener(new Button.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
final Menu menu = mPopupMenu.getMenu(); showMenu();
// 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();
} }
}); });
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() { private void addTab() {
@ -427,7 +431,7 @@ public class TabsPanel extends LinearLayout
mAddTab.setVisibility(View.INVISIBLE); mAddTab.setVisibility(View.INVISIBLE);
mMenuButton.setVisibility(View.INVISIBLE); mMenuButton.setVisibility(View.GONE);
} else { } else {
if (mFooter != null) if (mFooter != null)
mFooter.setVisibility(View.VISIBLE); mFooter.setVisibility(View.VISIBLE);
@ -435,8 +439,13 @@ public class TabsPanel extends LinearLayout
mAddTab.setVisibility(View.VISIBLE); mAddTab.setVisibility(View.VISIBLE);
mAddTab.setImageLevel(index); 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()) { if (isSideBar()) {

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

@ -4157,6 +4157,13 @@ Tab.prototype = {
if (!sameDocument) { if (!sameDocument) {
// XXX This code assumes that this is the earliest hook we have at which // 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 // 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.contentDocumentIsDisplayed = false;
this.hasTouchListener = false; this.hasTouchListener = false;
} else { } else {
@ -4460,9 +4467,11 @@ Tab.prototype = {
}); });
}, },
setBrowserSize: function(aWidth, aHeight) { setBrowserSize: function(aWidth, aHeight, aForce) {
if (fuzzyEquals(this.browserWidth, aWidth) && fuzzyEquals(this.browserHeight, aHeight)) { if (!aForce) {
return; if (fuzzyEquals(this.browserWidth, aWidth) && fuzzyEquals(this.browserHeight, aHeight)) {
return;
}
} }
this.browserWidth = aWidth; this.browserWidth = aWidth;

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

@ -1630,6 +1630,10 @@ deniedTranslationOffer
Integer count of the number of times the user opted-out offered 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 page translation, either by the Not Now button or by the notification's
close button in the "offer" state. 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 showOriginalContent
Integer count of the number of times the user activated the Show Original Integer count of the number of times the user activated the Show Original
command. command.
@ -1672,6 +1676,7 @@ Example
"detectedLanguageChangedAfter": 2, "detectedLanguageChangedAfter": 2,
"targetLanguageChanged": 0, "targetLanguageChanged": 0,
"deniedTranslationOffer": 3, "deniedTranslationOffer": 3,
"autoRejectedTranlationOffer": 1,
"showOriginalContent": 2, "showOriginalContent": 2,
"translationOpportunityCountsByLanguage": { "translationOpportunityCountsByLanguage": {
"fr": 100, "fr": 100,

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

@ -61,7 +61,6 @@ let initTable = [
["io", "@mozilla.org/network/io-service;1", "nsIIOService2"], ["io", "@mozilla.org/network/io-service;1", "nsIIOService2"],
["locale", "@mozilla.org/intl/nslocaleservice;1", "nsILocaleService"], ["locale", "@mozilla.org/intl/nslocaleservice;1", "nsILocaleService"],
["logins", "@mozilla.org/login-manager;1", "nsILoginManager"], ["logins", "@mozilla.org/login-manager;1", "nsILoginManager"],
["netutil", "@mozilla.org/network/util;1", "nsINetUtil"],
["obs", "@mozilla.org/observer-service;1", "nsIObserverService"], ["obs", "@mozilla.org/observer-service;1", "nsIObserverService"],
["perms", "@mozilla.org/permissionmanager;1", "nsIPermissionManager"], ["perms", "@mozilla.org/permissionmanager;1", "nsIPermissionManager"],
["prompt", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService"], ["prompt", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService"],