/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ // Tests which icon URLs UrlbarUtils.getRemoteImageUrl hands on as they are and // which it wraps in `moz-remote-image:` so the image decodes outside the // parent process (bug 2012436). "use strict"; const SIZE = 16; // The icon of a rich suggestion, as the search provider sends it. const DATA_URL = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; // A controller whose view renders in a content process, which decodes what it // displays itself. const CONTENT_CONTROLLER = { rendersInContentProcess: true }; /** * Asserts that an icon URL came back wrapped in `moz-remote-image:`. * * @param {string} iconUrl * The URL passed to `getRemoteIconUrl`. * @param {?string} result * What `getRemoteIconUrl` returned. * @return {URL} * A `URL` object created from `result`. */ function assertWrapped(iconUrl, result) { let url = URL.parse(result); Assert.equal(url?.protocol, "moz-remote-image:", `${iconUrl} is wrapped`); Assert.equal( url.searchParams.get("url"), iconUrl, "The wrapper carries the original URL" ); return url; } add_task(function trustedSchemesPassThrough() { // These are the browser's own images, so there is nothing to re-encode. for (let iconUrl of [ "chrome://global/skin/icons/search-glass.svg", "about:logo", "resource://content-accessible/moz.png", ]) { Assert.equal( UrlbarUtils.getRemoteImageUrl({ url: iconUrl, size: SIZE }), iconUrl, `${iconUrl} is used as it is` ); } }); add_task(function untrustedSchemesAreWrapped() { for (let iconUrl of [ DATA_URL, "https://example.com/favicon.ico", "http://example.com/favicon.ico", ]) { assertWrapped( iconUrl, UrlbarUtils.getRemoteImageUrl({ url: iconUrl, size: SIZE }) ); } }); add_task(function unexpectedSchemesAreWrapped() { // A provider can send anything. None of these reach an unwrapped, and // `moz-remote-image:` produces no image for them. for (let iconUrl of [ "javascript:alert(1)", "file:///etc/passwd", "ftp://example.com/favicon.ico", "blob:https://example.com/6a1b2c3d", "moz-remote-image://?url=https%3A%2F%2Fexample.com%2Ffavicon.ico", ]) { assertWrapped( iconUrl, UrlbarUtils.getRemoteImageUrl({ url: iconUrl, size: SIZE }) ); } }); add_task(function nonUrlsYieldNull() { for (let iconUrl of [ "", "not a url", "example.com/favicon.ico", "//host/x", ]) { Assert.equal( UrlbarUtils.getRemoteImageUrl({ url: iconUrl, size: SIZE }), null, `${iconUrl} is not a URL` ); } }); add_task(function contentProcessViewTakesTheIconAsItIs() { for (let iconUrl of [ DATA_URL, "https://example.com/favicon.ico", "chrome://global/skin/icons/search-glass.svg", ]) { Assert.equal( UrlbarUtils.getRemoteImageUrl({ url: iconUrl, size: SIZE, controller: CONTENT_CONTROLLER, }), iconUrl, `${iconUrl} is used as it is` ); } // The scheme check the wrapper would apply doesn't apply here, but the page // still can't load these -- http because its CSP allows only https:, data:, // blob: and chrome: -- so the row falls back to a broken-image icon. for (let iconUrl of [ "javascript:alert(1)", "file:///etc/passwd", "http://example.com/favicon.ico", ]) { Assert.equal( UrlbarUtils.getRemoteImageUrl({ url: iconUrl, size: SIZE, controller: CONTENT_CONTROLLER, }), iconUrl, `${iconUrl} is used as it is` ); } Assert.equal( UrlbarUtils.getRemoteImageUrl({ url: "not a url", size: SIZE, controller: CONTENT_CONTROLLER, }), null, "A string that isn't a URL is still rejected" ); }); add_task(function noSize() { let bareUrl = "https://example.com/no-size"; let wrappedUrl = UrlbarUtils.getRemoteImageUrl({ url: bareUrl }); let parsedUrl = assertWrapped(bareUrl, wrappedUrl); Assert.ok( !parsedUrl.searchParams.has("size"), "The created moz-remote-image URL should not have a `size` search param" ); });