--- a/browser/base/content/test/favicons/browser.ini
+++ b/browser/base/content/test/favicons/browser.ini
@@ -1,6 +1,13 @@
[DEFAULT]
support-files =
head.js
discovery.html
+ file_rich_icon.html
+ file_mask_icon.html
+ moz.png
+ rich_moz_1.png
+ rich_moz_2.png
[browser_multiple_icons_in_short_timeframe.js]
+[browser_rich_icons.js]
+[browser_icon_discovery.js]
rename from browser/base/content/test/general/browser_discovery.js
rename to browser/base/content/test/favicons/browser_icon_discovery.js
--- a/browser/base/content/test/general/browser_discovery.js
+++ b/browser/base/content/test/favicons/browser_icon_discovery.js
@@ -1,50 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
/* eslint-disable mozilla/no-arbitrary-setTimeout */
add_task(async function() {
- let url = "http://mochi.test:8888/browser/browser/base/content/test/general/discovery.html";
+ let url = "http://mochi.test:8888/browser/browser/base/content/test/favicons/discovery.html";
info("Test icons discovery");
// First we need to clear the failed favicons cache, since previous tests
// likely added this non-existing icon, and useDefaultIcon would skip it.
PlacesUtils.favicons.removeFailedFavicon(makeURI("http://mochi.test:8888/favicon.ico"));
await BrowserTestUtils.withNewTab(url, iconDiscovery);
- info("Test search discovery");
- await BrowserTestUtils.withNewTab(url, searchDiscovery);
});
let iconDiscoveryTests = [
{ text: "rel icon discovered" },
{ rel: "abcdefg icon qwerty", text: "rel may contain additional rels separated by spaces" },
{ rel: "ICON", text: "rel is case insensitive" },
{ rel: "shortcut-icon", pass: false, text: "rel shortcut-icon not discovered" },
{ href: "moz.png", text: "relative href works" },
{ href: "notthere.png", text: "404'd icon is removed properly" },
{ href: "data:image/x-icon,%00", type: "image/x-icon", text: "data: URIs work" },
{ type: "image/png; charset=utf-8", text: "type may have optional parameters (RFC2046)" },
- // These rich icon tests are temporarily disabled due to intermittent failures.
- /*
{ richIcon: true, rel: "apple-touch-icon", text: "apple-touch-icon discovered" },
{ richIcon: true, rel: "apple-touch-icon-precomposed", text: "apple-touch-icon-precomposed discovered" },
{ richIcon: true, rel: "fluid-icon", text: "fluid-icon discovered" },
- */
{ richIcon: true, rel: "unknown-icon", pass: false, text: "unknown icon not discovered" }
];
async function iconDiscovery() {
// Since the page doesn't have an icon, we should try using the root domain
// icon.
await BrowserTestUtils.waitForCondition(() => {
return gBrowser.getIcon() == "http://mochi.test:8888/favicon.ico";
}, "wait for default icon load to finish");
for (let testCase of iconDiscoveryTests) {
if (testCase.pass == undefined)
testCase.pass = true;
- testCase.rootDir = getRootDirectory(gTestPath);
// Clear the current icon.
gBrowser.setIcon(gBrowser.selectedTab, null);
let promiseLinkAdded =
BrowserTestUtils.waitForContentEvent(gBrowser.selectedBrowser, "DOMLinkAdded",
false, null, true);
let promiseMessage = new Promise(resolve => {
@@ -55,17 +52,17 @@ async function iconDiscovery() {
});
});
await ContentTask.spawn(gBrowser.selectedBrowser, testCase, test => {
let doc = content.document;
let head = doc.getElementById("linkparent");
let link = doc.createElement("link");
link.rel = test.rel || "icon";
- link.href = test.href || test.rootDir + "moz.png";
+ link.href = test.href || "http://mochi.test:8888/browser/browser/base/content/test/favicons/moz.png";
link.type = test.type || "image/png";
head.appendChild(link);
});
await promiseLinkAdded;
if (!testCase.richIcon) {
// Because there is debounce logic in ContentLinkHandler.jsm to reduce the
@@ -92,90 +89,8 @@ async function iconDiscovery() {
}
await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
let head = content.document.getElementById("linkparent");
head.removeChild(head.getElementsByTagName("link")[0]);
});
}
}
-
-let searchDiscoveryTests = [
- { text: "rel search discovered" },
- { rel: "SEARCH", text: "rel is case insensitive" },
- { rel: "-search-", pass: false, text: "rel -search- not discovered" },
- { rel: "foo bar baz search quux", text: "rel may contain additional rels separated by spaces" },
- { href: "https://not.mozilla.com", text: "HTTPS ok" },
- { href: "ftp://not.mozilla.com", text: "FTP ok" },
- { href: "data:text/foo,foo", pass: false, text: "data URI not permitted" },
- { href: "javascript:alert(0)", pass: false, text: "JS URI not permitted" },
- { type: "APPLICATION/OPENSEARCHDESCRIPTION+XML", text: "type is case insensitve" },
- { type: " application/opensearchdescription+xml ", text: "type may contain extra whitespace" },
- { type: "application/opensearchdescription+xml; charset=utf-8", text: "type may have optional parameters (RFC2046)" },
- { type: "aapplication/opensearchdescription+xml", pass: false, text: "type should not be loosely matched" },
- { rel: "search search search", count: 1, text: "only one engine should be added" }
-];
-
-async function searchDiscovery() {
- let browser = gBrowser.selectedBrowser;
-
- for (let testCase of searchDiscoveryTests) {
- if (testCase.pass == undefined)
- testCase.pass = true;
- testCase.title = testCase.title || searchDiscoveryTests.indexOf(testCase);
-
- let promiseLinkAdded =
- BrowserTestUtils.waitForContentEvent(gBrowser.selectedBrowser, "DOMLinkAdded",
- false, null, true);
-
- await ContentTask.spawn(gBrowser.selectedBrowser, testCase, test => {
- let doc = content.document;
- let head = doc.getElementById("linkparent");
- let link = doc.createElement("link");
- link.rel = test.rel || "search";
- link.href = test.href || "http://so.not.here.mozilla.com/search.xml";
- link.type = test.type || "application/opensearchdescription+xml";
- link.title = test.title;
- head.appendChild(link);
- });
-
- await promiseLinkAdded;
- await new Promise(resolve => executeSoon(resolve));
-
- if (browser.engines) {
- info(`Found ${browser.engines.length} engines`);
- info(`First engine title: ${browser.engines[0].title}`);
- let hasEngine = testCase.count ?
- (browser.engines[0].title == testCase.title && browser.engines.length == testCase.count) :
- (browser.engines[0].title == testCase.title);
- ok(hasEngine, testCase.text);
- browser.engines = null;
- } else {
- ok(!testCase.pass, testCase.text);
- }
- }
-
- info("Test multiple engines with the same title");
- let promiseLinkAdded =
- BrowserTestUtils.waitForContentEvent(gBrowser.selectedBrowser, "DOMLinkAdded",
- false, e => e.target.href == "http://second.mozilla.com/search.xml", true);
- await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
- let doc = content.document;
- let head = doc.getElementById("linkparent");
- let link = doc.createElement("link");
- link.rel = "search";
- link.href = "http://first.mozilla.com/search.xml";
- link.type = "application/opensearchdescription+xml";
- link.title = "Test Engine";
- let link2 = link.cloneNode(false);
- link2.href = "http://second.mozilla.com/search.xml";
- head.appendChild(link);
- head.appendChild(link2);
- });
-
- await promiseLinkAdded;
- await new Promise(resolve => executeSoon(resolve));
-
- ok(browser.engines, "has engines");
- is(browser.engines.length, 1, "only one engine");
- is(browser.engines[0].uri, "http://first.mozilla.com/search.xml", "first engine wins");
- browser.engines = null;
-}
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/favicons/browser_rich_icons.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+
+const ROOT = "http://mochi.test:8888/browser/browser/base/content/test/favicons/"
+
+add_task(async function test_richIcons() {
+ const URL = ROOT + "file_rich_icon.html";
+ const EXPECTED_ICON = ROOT + "moz.png";
+ const EXPECTED_RICH_ICON = ROOT + "rich_moz_2.png";
+ // One regular icon and one rich icon. Note that ContentLinkHandler will
+ // choose the best rich icon if there are multiple candidates available
+ // in the page.
+ const EXPECTED_ICON_LOADS = 2;
+ let loadCount = 0;
+ let tabIconUri;
+ let richIconUri;
+
+ const promiseMessage = new Promise(resolve => {
+ const mm = window.messageManager;
+ mm.addMessageListener("Link:SetIcon", function listenForSetIcon(msg) {
+ // Ignore the chrome favicon sets on the tab before the actual page load.
+ if (msg.data.url === "chrome://branding/content/icon32.png")
+ return;
+
+ if (!msg.data.canUseForTab)
+ richIconUri = msg.data.url;
+
+ if (++loadCount === EXPECTED_ICON_LOADS) {
+ mm.removeMessageListener("Link:SetIcon", listenForSetIcon);
+ resolve();
+ }
+ });
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+ await promiseMessage;
+
+ is(richIconUri, EXPECTED_RICH_ICON, "should choose the largest rich icon");
+
+ // Because there is debounce logic in ContentLinkHandler.jsm to reduce the
+ // favicon loads, we have to wait some time before checking that icon was
+ // stored properly.
+ await BrowserTestUtils.waitForCondition(() => {
+ tabIconUri = gBrowser.getIcon();
+ return tabIconUri != null;
+ }, "wait for icon load to finish", 100, 20);
+ is(tabIconUri, EXPECTED_ICON, "should use the non-rich icon for the tab");
+ await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_maskIcons() {
+ const URL = ROOT + "file_mask_icon.html";
+ // First we need to clear the failed favicons cache, since previous tests
+ // likely added this non-existing icon, and useDefaultIcon would skip it.
+ PlacesUtils.favicons.removeFailedFavicon(makeURI("http://mochi.test:8888/favicon.ico"));
+ const EXPECTED_ICON = "http://mochi.test:8888/favicon.ico";
+ let tabIconUri;
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+
+ await BrowserTestUtils.waitForCondition(() => {
+ tabIconUri = gBrowser.getIcon();
+ return tabIconUri != null;
+ }, "wait for icon load to finish", 100, 20);
+ is(tabIconUri, EXPECTED_ICON, "should ignore the mask icons and load the root favicon");
+ await BrowserTestUtils.removeTab(tab);
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/favicons/file_mask_icon.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="UTF-8" />
+ <title>Mask Icon</title>
+ <link rel="icon" mask href="moz.png" type="image/png" />
+ <link rel="mask-icon" href="moz.png" type="image/png" />
+ </head>
+ <body>
+ </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/favicons/file_rich_icon.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="UTF-8" />
+ <title>Rich Icons</title>
+ <link rel="icon" href="moz.png" type="image/png" />
+ <link rel="apple-touch-icon" sizes="96x96" href="rich_moz_1.png" type="image/png" />
+ <link rel="apple-touch-icon" sizes="256x256" href="rich_moz_2.png" type="image/png" />
+ </head>
+ <body>
+ </body>
+</html>
--- a/browser/base/content/test/favicons/head.js
+++ b/browser/base/content/test/favicons/head.js
@@ -2,8 +2,10 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils",
"resource://testing-common/BrowserTestUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "ContentTask",
"resource://testing-common/ContentTask.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
new file mode 100644
index 0000000000000000000000000000000000000000..769c636340e11f9d2a0b7eb6a84d574dd9563f0c
GIT binary patch
literal 580
zc$@)50=xZ*P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz)=5M`RCwBA
z{Qv(y10?_;fS8au@87>?@9gaSt*os4-<6T^zln*-e<1(Y>eZ{a-@A8D7MlS80mJ}u
z0SKQtbH>fs*!aH-1H=C_K)ecw?*efe5W7I}s#U9Y!PLVrKmdV>nKNfT1{(H16si$K
zmjm&CV+al6D=8`c7X*o+82}JK4A3z64>JJB_`e(E3S)?2<zN>X{|^lf1sei(+1<Me
zFarPr2&}oIqvIC?)OL^o?-&p^!wh-{#k-;2f_VoZfS{%bLi}zFF<>UtAY{W<LBj?n
zz6$CsfB=HI;P*^44eyZn#$YcBg1rF~2L~`P&;bI75#$0;v@zVf$It;Z4d`qJS0Dzu
zh@l)BQx!mb7Roj*FK2kaXAgtR*|Q9LfP8=e0=od{pY0$UI-sVXf!f>w#jt?wfcn3@
zy!=0d5|Evi_8%aC7~Z{m#}1AKK|~<_M{=g1px}QcP=ErR57Iaj7$e5OumVLr$n^jL
z1djz!sJcLHd57c@W2lWFi_p^m2m=HVoB>kgf@C|$pqa*ykjAAMgaHBwo)>`0c=vmt
z+g3yQpuk*x7A(#H^u|wInF%0(FiZq_r2`t3pnwGh6fWCA7$ATcDb3CR0R{jJCzQv)
SYsoAC0000<MNUMnLSTYrIq9PS
new file mode 100644
index 0000000000000000000000000000000000000000..769c636340e11f9d2a0b7eb6a84d574dd9563f0c
GIT binary patch
literal 580
zc$@)50=xZ*P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz)=5M`RCwBA
z{Qv(y10?_;fS8au@87>?@9gaSt*os4-<6T^zln*-e<1(Y>eZ{a-@A8D7MlS80mJ}u
z0SKQtbH>fs*!aH-1H=C_K)ecw?*efe5W7I}s#U9Y!PLVrKmdV>nKNfT1{(H16si$K
zmjm&CV+al6D=8`c7X*o+82}JK4A3z64>JJB_`e(E3S)?2<zN>X{|^lf1sei(+1<Me
zFarPr2&}oIqvIC?)OL^o?-&p^!wh-{#k-;2f_VoZfS{%bLi}zFF<>UtAY{W<LBj?n
zz6$CsfB=HI;P*^44eyZn#$YcBg1rF~2L~`P&;bI75#$0;v@zVf$It;Z4d`qJS0Dzu
zh@l)BQx!mb7Roj*FK2kaXAgtR*|Q9LfP8=e0=od{pY0$UI-sVXf!f>w#jt?wfcn3@
zy!=0d5|Evi_8%aC7~Z{m#}1AKK|~<_M{=g1px}QcP=ErR57Iaj7$e5OumVLr$n^jL
z1djz!sJcLHd57c@W2lWFi_p^m2m=HVoB>kgf@C|$pqa*ykjAAMgaHBwo)>`0c=vmt
z+g3yQpuk*x7A(#H^u|wInF%0(FiZq_r2`t3pnwGh6fWCA7$ATcDb3CR0R{jJCzQv)
SYsoAC0000<MNUMnLSTYrIq9PS
new file mode 100644
index 0000000000000000000000000000000000000000..769c636340e11f9d2a0b7eb6a84d574dd9563f0c
GIT binary patch
literal 580
zc$@)50=xZ*P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004XF*Lt006JZ
zHwB960000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz)=5M`RCwBA
z{Qv(y10?_;fS8au@87>?@9gaSt*os4-<6T^zln*-e<1(Y>eZ{a-@A8D7MlS80mJ}u
z0SKQtbH>fs*!aH-1H=C_K)ecw?*efe5W7I}s#U9Y!PLVrKmdV>nKNfT1{(H16si$K
zmjm&CV+al6D=8`c7X*o+82}JK4A3z64>JJB_`e(E3S)?2<zN>X{|^lf1sei(+1<Me
zFarPr2&}oIqvIC?)OL^o?-&p^!wh-{#k-;2f_VoZfS{%bLi}zFF<>UtAY{W<LBj?n
zz6$CsfB=HI;P*^44eyZn#$YcBg1rF~2L~`P&;bI75#$0;v@zVf$It;Z4d`qJS0Dzu
zh@l)BQx!mb7Roj*FK2kaXAgtR*|Q9LfP8=e0=od{pY0$UI-sVXf!f>w#jt?wfcn3@
zy!=0d5|Evi_8%aC7~Z{m#}1AKK|~<_M{=g1px}QcP=ErR57Iaj7$e5OumVLr$n^jL
z1djz!sJcLHd57c@W2lWFi_p^m2m=HVoB>kgf@C|$pqa*ykjAAMgaHBwo)>`0c=vmt
z+g3yQpuk*x7A(#H^u|wInF%0(FiZq_r2`t3pnwGh6fWCA7$ATcDb3CR0R{jJCzQv)
SYsoAC0000<MNUMnLSTYrIq9PS
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -321,17 +321,17 @@ skip-if = toolkit == "gtk2" || toolkit =
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_ctrlTab.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_datachoices_notification.js]
skip-if = !datareporting
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_decoderDoctor.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
-[browser_discovery.js]
+[browser_search_discovery.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_double_close_tab.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_documentnavigation.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_duplicateIDs.js]
# DO NOT ADD MORE TESTS HERE. USE A TOPICAL DIRECTORY INSTEAD.
[browser_drag.js]
copy from browser/base/content/test/general/browser_discovery.js
copy to browser/base/content/test/general/browser_search_discovery.js
--- a/browser/base/content/test/general/browser_discovery.js
+++ b/browser/base/content/test/general/browser_search_discovery.js
@@ -1,108 +1,17 @@
-/* eslint-disable mozilla/no-arbitrary-setTimeout */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(async function() {
let url = "http://mochi.test:8888/browser/browser/base/content/test/general/discovery.html";
- info("Test icons discovery");
- // First we need to clear the failed favicons cache, since previous tests
- // likely added this non-existing icon, and useDefaultIcon would skip it.
- PlacesUtils.favicons.removeFailedFavicon(makeURI("http://mochi.test:8888/favicon.ico"));
- await BrowserTestUtils.withNewTab(url, iconDiscovery);
info("Test search discovery");
await BrowserTestUtils.withNewTab(url, searchDiscovery);
});
-let iconDiscoveryTests = [
- { text: "rel icon discovered" },
- { rel: "abcdefg icon qwerty", text: "rel may contain additional rels separated by spaces" },
- { rel: "ICON", text: "rel is case insensitive" },
- { rel: "shortcut-icon", pass: false, text: "rel shortcut-icon not discovered" },
- { href: "moz.png", text: "relative href works" },
- { href: "notthere.png", text: "404'd icon is removed properly" },
- { href: "data:image/x-icon,%00", type: "image/x-icon", text: "data: URIs work" },
- { type: "image/png; charset=utf-8", text: "type may have optional parameters (RFC2046)" },
- // These rich icon tests are temporarily disabled due to intermittent failures.
- /*
- { richIcon: true, rel: "apple-touch-icon", text: "apple-touch-icon discovered" },
- { richIcon: true, rel: "apple-touch-icon-precomposed", text: "apple-touch-icon-precomposed discovered" },
- { richIcon: true, rel: "fluid-icon", text: "fluid-icon discovered" },
- */
- { richIcon: true, rel: "unknown-icon", pass: false, text: "unknown icon not discovered" }
-];
-
-async function iconDiscovery() {
- // Since the page doesn't have an icon, we should try using the root domain
- // icon.
- await BrowserTestUtils.waitForCondition(() => {
- return gBrowser.getIcon() == "http://mochi.test:8888/favicon.ico";
- }, "wait for default icon load to finish");
-
- for (let testCase of iconDiscoveryTests) {
- if (testCase.pass == undefined)
- testCase.pass = true;
- testCase.rootDir = getRootDirectory(gTestPath);
-
- // Clear the current icon.
- gBrowser.setIcon(gBrowser.selectedTab, null);
-
- let promiseLinkAdded =
- BrowserTestUtils.waitForContentEvent(gBrowser.selectedBrowser, "DOMLinkAdded",
- false, null, true);
- let promiseMessage = new Promise(resolve => {
- let mm = window.messageManager;
- mm.addMessageListener("Link:SetIcon", function listenForIcon(msg) {
- mm.removeMessageListener("Link:SetIcon", listenForIcon);
- resolve(msg.data);
- });
- });
-
- await ContentTask.spawn(gBrowser.selectedBrowser, testCase, test => {
- let doc = content.document;
- let head = doc.getElementById("linkparent");
- let link = doc.createElement("link");
- link.rel = test.rel || "icon";
- link.href = test.href || test.rootDir + "moz.png";
- link.type = test.type || "image/png";
- head.appendChild(link);
- });
-
- await promiseLinkAdded;
-
- if (!testCase.richIcon) {
- // Because there is debounce logic in ContentLinkHandler.jsm to reduce the
- // favicon loads, we have to wait some time before checking that icon was
- // stored properly.
- try {
- await BrowserTestUtils.waitForCondition(() => {
- return gBrowser.getIcon() != null;
- }, "wait for icon load to finish", 100, 20);
- ok(testCase.pass, testCase.text);
- } catch (ex) {
- ok(!testCase.pass, testCase.text);
- }
- } else {
- // Rich icons are not set as tab icons, so just check for the SetIcon message.
- try {
- let data = await Promise.race([promiseMessage,
- new Promise((resolve, reject) => setTimeout(reject, 2000))]);
- is(data.canUseForTab, false, "Rich icons cannot be used for tabs");
- ok(testCase.pass, testCase.text);
- } catch (ex) {
- ok(!testCase.pass, testCase.text);
- }
- }
-
- await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
- let head = content.document.getElementById("linkparent");
- head.removeChild(head.getElementsByTagName("link")[0]);
- });
- }
-}
-
let searchDiscoveryTests = [
{ text: "rel search discovered" },
{ rel: "SEARCH", text: "rel is case insensitive" },
{ rel: "-search-", pass: false, text: "rel -search- not discovered" },
{ rel: "foo bar baz search quux", text: "rel may contain additional rels separated by spaces" },
{ href: "https://not.mozilla.com", text: "HTTPS ok" },
{ href: "ftp://not.mozilla.com", text: "FTP ok" },
{ href: "data:text/foo,foo", pass: false, text: "data URI not permitted" },