Bug 1682593: Remove the site specific browser feature. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D101860
This commit is contained in:
Dave Townsend 2021-01-15 11:11:19 +00:00
Родитель 383977e4ac
Коммит 2459f40c64
53 изменённых файлов: 0 добавлений и 3376 удалений

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

@ -2030,9 +2030,6 @@ pref("app.normandy.onsync_skew_sec", 600);
pref("app.shield.optoutstudies.enabled", false);
#endif
// Web apps support
pref("browser.ssb.enabled", false);
// Multi-lingual preferences
#if defined(RELEASE_OR_BETA) && !defined(MOZ_DEV_EDITION)
pref("intl.multilingual.enabled", true);

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

@ -2,11 +2,6 @@
* 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/. */
ChromeUtils.defineModuleGetter(
this,
"SiteSpecificBrowser",
"resource:///modules/SiteSpecificBrowserService.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"SearchUIUtils",
@ -1133,33 +1128,6 @@ BrowserPageActions.pinTab = {
},
};
// SiteSpecificBrowser
BrowserPageActions.launchSSB = {
updateState() {
let action = PageActions.actionForID("launchSSB");
let browser = gBrowser.selectedBrowser;
action.setDisabled(!browser.currentURI.schemeIs("https"), window);
},
async onCommand(event, buttonNode) {
if (!gBrowser.currentURI.schemeIs("https")) {
return;
}
let ssb = await SiteSpecificBrowser.createFromBrowser(
gBrowser.selectedBrowser
);
// Launching through the UI implies installing.
await ssb.install();
// The site's manifest may point to a different start page so explicitly
// open the SSB to the current page.
ssb.launch(gBrowser.selectedBrowser.currentURI);
gBrowser.removeTab(gBrowser.selectedTab, { closeWindowWithLastTab: false });
},
};
// copy URL
BrowserPageActions.copyURL = {
onCommand(event, buttonNode) {

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

@ -70,9 +70,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
SimpleServiceDiscovery: "resource://gre/modules/SimpleServiceDiscovery.jsm",
SiteDataManager: "resource:///modules/SiteDataManager.jsm",
SitePermissions: "resource:///modules/SitePermissions.jsm",
SiteSpecificBrowser: "resource:///modules/SiteSpecificBrowserService.jsm",
SiteSpecificBrowserService:
"resource:///modules/SiteSpecificBrowserService.jsm",
SubDialogManager: "resource://gre/modules/SubDialog.jsm",
TabModalPrompt: "chrome://global/content/tabprompts.jsm",
TabCrashHandler: "resource:///modules/ContentCrashHandlers.jsm",
@ -1930,8 +1927,6 @@ var gBrowserInit = {
FullZoom.init();
PanelUI.init();
SiteSpecificBrowserUI.init();
UpdateUrlbarSearchSplitterState();
BookmarkingUI.init();
@ -2567,130 +2562,6 @@ gBrowserInit.idleTasksFinishedPromise = new Promise(resolve => {
gBrowserInit.idleTaskPromiseResolve = resolve;
});
const SiteSpecificBrowserUI = {
menuInitialized: false,
init() {
if (!SiteSpecificBrowserService.isEnabled) {
return;
}
XPCOMUtils.defineLazyGetter(this, "panelBody", () => {
return PanelMultiView.getViewNode(
document,
"appMenu-SSBView .panel-subview-body"
);
});
let initializeMenu = async () => {
let list = await SiteSpecificBrowserService.list();
for (let ssb of list) {
this.addSSBToMenu(ssb);
}
if (!list.length) {
document.getElementById("appMenu-ssb-button").hidden = true;
}
this.menuInitialized = true;
Services.obs.addObserver(this, "site-specific-browser-install", true);
Services.obs.addObserver(this, "site-specific-browser-uninstall", true);
};
document.getElementById("appMenu-popup").addEventListener(
"popupshowing",
() => {
let blocker = initializeMenu();
PanelMultiView.getViewNode(
document,
"appMenu-SSBView"
).addEventListener(
"ViewShowing",
event => {
event.detail.addBlocker(blocker);
},
{ once: true }
);
},
{ once: true }
);
},
observe(subject, topic, id) {
let ssb = SiteSpecificBrowser.get(id);
switch (topic) {
case "site-specific-browser-install":
this.addSSBToMenu(ssb);
break;
case "site-specific-browser-uninstall":
this.removeSSBFromMenu(ssb);
break;
}
},
removeSSBFromMenu(ssb) {
let container = document.getElementById("ssb-button-" + ssb.id);
if (!container) {
return;
}
if (!container.nextElementSibling && !container.previousElementSibling) {
document.getElementById("appMenu-ssb-button").hidden = true;
}
let button = container.querySelector(".ssb-launch");
let uri = button.getAttribute("image");
if (uri) {
URL.revokeObjectURL(uri);
}
container.remove();
},
addSSBToMenu(ssb) {
let container = document.createXULElement("toolbaritem");
container.id = `ssb-button-${ssb.id}`;
container.className = "toolbaritem-menu-buttons";
let menu = document.createXULElement("toolbarbutton");
menu.className = "ssb-launch subviewbutton subviewbutton-iconic";
menu.setAttribute("label", ssb.name);
menu.setAttribute("flex", "1");
ssb.getScaledIcon(16 * devicePixelRatio).then(
icon => {
if (icon) {
menu.setAttribute("image", URL.createObjectURL(icon));
}
},
error => {
console.error(error);
}
);
menu.addEventListener("command", () => {
ssb.launch();
});
let uninstall = document.createXULElement("toolbarbutton");
uninstall.className = "ssb-uninstall subviewbutton subviewbutton-iconic";
// Hardcoded for now. Localization tracked in bug 1602528.
uninstall.setAttribute("tooltiptext", "Uninstall");
uninstall.addEventListener("command", () => {
ssb.uninstall();
});
container.append(menu);
container.append(uninstall);
this.panelBody.append(container);
document.getElementById("appMenu-ssb-button").hidden = false;
},
QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),
};
function HandleAppCommandEvent(evt) {
switch (evt.command) {
case "Back":

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

@ -868,12 +868,6 @@
key="key_openAddons"
command="Tools:Addons"
/>
<toolbarbutton id="appMenu-ssb-button"
class="subviewbutton subviewbutton-iconic subviewbutton-nav"
label="Sites in App Mode"
closemenu="none"
oncommand="PanelUI.showSubView('appMenu-SSBView', this)"
hidden="true" persist="hidden"/>
<toolbarbutton id="appMenu-preferences-button"
class="subviewbutton subviewbutton-iconic"
#ifdef XP_WIN
@ -1184,11 +1178,6 @@
</vbox>
</panelview>
<panelview id="appMenu-SSBView" class="PanelUI-subView">
<vbox class="panel-subview-body">
</vbox>
</panelview>
<panelview id="appMenu-moreView" title="&moreMenu.label;" class="PanelUI-subView">
<vbox class="panel-subview-body">
<toolbarbutton id="appMenu-taskmanager-button"

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

@ -691,17 +691,6 @@ let JSWINDOWACTORS = {
allFrames: true,
},
SiteSpecificBrowser: {
parent: {
moduleURI: "resource:///actors/SiteSpecificBrowserParent.jsm",
},
child: {
moduleURI: "resource:///actors/SiteSpecificBrowserChild.jsm",
},
allFrames: true,
},
Translation: {
parent: {
moduleURI: "resource:///modules/translation/TranslationParent.jsm",

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

@ -53,7 +53,6 @@ DIRS += [
"search",
"sessionstore",
"shell",
"ssb",
"syncedtabs",
"uitour",
"urlbar",

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

@ -1,137 +0,0 @@
/* 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/. */
var EXPORTED_SYMBOLS = ["ImageTools"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
FileUtils: "resource://gre/modules/FileUtils.jsm",
NetUtil: "resource://gre/modules/NetUtil.jsm",
Services: "resource://gre/modules/Services.jsm",
});
XPCOMUtils.defineLazyServiceGetter(
this,
"ImgTools",
"@mozilla.org/image/tools;1",
Ci.imgITools
);
XPCOMUtils.defineLazyGlobalGetters(this, ["Blob"]);
const ImageTools = {
/**
* Given a data URI decodes the data into an object with "type" which is the
* found mimetype and "container" which is an imgIContainer.
*
* @param {nsIURI} dataURI the URI to load.
* @return {Promise<object>} the image info.
*/
loadImage(dataURI) {
return new Promise((resolve, reject) => {
if (!dataURI.schemeIs("data")) {
reject(new Error("Should only be loading data URIs."));
return;
}
let channel = NetUtil.newChannel({
uri: dataURI,
loadUsingSystemPrincipal: true,
});
ImgTools.decodeImageFromChannelAsync(
dataURI,
channel,
(container, status) => {
if (Components.isSuccessCode(status)) {
resolve({
type: channel.contentType,
container,
});
} else {
reject(Components.Exception("Failed to load image.", status));
}
},
null
);
});
},
scaleImage(container, width, height) {
return new Promise((resolve, reject) => {
let stream = ImgTools.encodeScaledImage(
container,
"image/png",
width,
height,
""
);
try {
stream.QueryInterface(Ci.nsIAsyncInputStream);
} catch (e) {
reject(
Components.Exception(
"imgIEncoder must implement nsIAsyncInputStream",
e
)
);
}
let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
Ci.nsIBinaryInputStream
);
binaryStream.setInputStream(stream);
let buffers = [];
let callback = () => {
try {
let available = binaryStream.available();
if (available) {
let buffer = new ArrayBuffer(available);
binaryStream.readArrayBuffer(available, buffer);
buffers.push(buffer);
stream.asyncWait(callback, 0, 0, Services.tm.mainThread);
return;
}
// No data available, assume the encoding is done.
resolve(new Blob(buffers));
} catch (e) {
reject(e);
}
};
try {
stream.asyncWait(callback, 0, 0, Services.tm.mainThread);
} catch (e) {
reject(e);
}
});
},
saveIcon(container, width, height, target) {
return new Promise((resolve, reject) => {
let output = FileUtils.openFileOutputStream(target);
let stream = ImgTools.encodeScaledImage(
container,
"image/vnd.microsoft.icon",
width,
height,
""
);
NetUtil.asyncCopy(stream, output, status => {
if (Components.isSuccessCode(status)) {
resolve();
} else {
reject(Components.Exception("Failed to save icon.", status));
}
});
});
},
};

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

@ -1,177 +0,0 @@
/* 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/. */
"use strict";
const EXPORTED_SYMBOLS = ["SiteSpecificBrowserChild"];
const { SiteSpecificBrowserBase } = ChromeUtils.import(
"resource:///modules/SiteSpecificBrowserService.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { E10SUtils } = ChromeUtils.import(
"resource://gre/modules/E10SUtils.jsm"
);
/**
* Loads an icon URL into a data URI.
*
* @param {Window} window the DOM window providing the icon.
* @param {string} uri the href for the icon, may be relative to the source page.
* @return {Promise<string>} the data URI.
*/
async function loadIcon(window, uri) {
let iconURL = new window.URL(uri, window.location);
let request = new window.Request(iconURL, { mode: "cors" });
request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_IMAGE);
let response = await window.fetch(request);
let blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
class SiteSpecificBrowserChild extends JSWindowActorChild {
receiveMessage(message) {
switch (message.name) {
case "SetSSB":
// Note that this sets the webbrowserchrome for the top-level browser
// child in this page. This means that any inner-frames loading in
// different processes will not be handled correctly. Fixing this will
// happen in bug 1602849.
this.docShell.browserChild.webBrowserChrome = new WebBrowserChrome(
message.data
);
break;
case "LoadIcon":
return loadIcon(this.contentWindow, message.data);
}
return null;
}
}
function getActor(docShell) {
return docShell.domWindow.windowGlobalChild.getActor("SiteSpecificBrowser");
}
// JS actors can't generally be XPCOM objects so we must use a separate class.
class WebBrowserChrome {
constructor(id) {
this.id = id;
}
get ssb() {
return SiteSpecificBrowserBase.get(this.id);
}
// nsIWebBrowserChrome3
/**
* This gets called when a user clicks on a link or submits a form. We can use
* it to see where the resulting page load will occur and if needed redirect
* it to a different target.
*
* @param {string} originalTarget the target intended for the load.
* @param {nsIURI} linkURI the URI that will be loaded.
* @param {Node} linkNode the element causing the load.
* @param {boolean} isAppTab whether the source docshell is marked as an
* app tab.
* @return {string} the target to use for the load.
*/
onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab) {
// Our actor is for the top-level frame in the page while this may be being
// called for a navigation in an inner frame. First we have to find the
// browsing context for the frame doing the load.
let docShell = linkNode.ownerGlobal.docShell;
let bc = docShell.browsingContext;
// Which browsing context is this link targetting?
let target = originalTarget ? bc.findWithName(originalTarget) : bc;
if (target) {
// If we found a target then it must be one of the frames within this
// frame tree since we don't support popup windows.
if (target.parent) {
// An inner frame, continue.
return originalTarget;
}
// A top-level load. If our SSB cannot load this URI then start the
// process of opening it into a new tab somewhere.
return this.ssb.canLoad(linkURI) ? originalTarget : "_blank";
}
// An attempt to open a new window/tab. If the new URI can be loaded by our
// SSB then load it at the top-level. Note that we override the requested
// target so that this page can't reach the new context.
return this.ssb.canLoad(linkURI) ? "_top" : "_blank";
}
/**
* A load is about to occur in a frame. This is an opportunity to stop it
* and redirect it somewhere.
*
* @param {nsIDocShell} docShell the current docshell.
* @param {nsIURI} uri the URI that will be loaded.
* @param {nsIReferrerInfo} referrerInfo the referrer info.
* @param {boolean} hasPostData whether there is POST data
* for the load.
* @param {nsIPrincipal} triggeringPrincipal the triggering principal.
* @param {nsIContentSecurityPolicy} csp the content security policy.
* @return {boolean} whether the load should proceed or not.
*/
shouldLoadURI(
docShell,
uri,
referrerInfo,
hasPostData,
triggeringPrincipal,
csp
) {
// As above, our actor is for the top-level frame in the page however we
// are passed the docshell potentially handling the load here so we can
// do the right thing.
// We only police loads at the top level.
if (docShell.browsingContext.parent) {
return true;
}
if (!this.ssb.canLoad(uri)) {
// Should only have got this far for a window.location manipulation.
getActor(docShell).sendAsyncMessage("RetargetOutOfScopeURIToBrowser", {
uri: uri.spec,
referrerInfo: E10SUtils.serializeReferrerInfo(referrerInfo),
triggeringPrincipal: E10SUtils.serializePrincipal(
triggeringPrincipal ||
Services.scriptSecurityManager.createNullPrincipal({})
),
csp: csp ? E10SUtils.serializeCSP(csp) : null,
});
return false;
}
return true;
}
/**
* A simple check for whether this is the correct process to load this URI.
*
* @param {nsIURI} uri the URI that will be loaded.
* @return {boolean} whether the load should proceed or not.
*/
shouldLoadURIInThisProcess(uri) {
return this.ssb.canLoad(uri);
}
}

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

@ -1,75 +0,0 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
"use strict";
var EXPORTED_SYMBOLS = ["SiteSpecificBrowserParent"];
const { BrowserWindowTracker } = ChromeUtils.import(
"resource:///modules/BrowserWindowTracker.jsm"
);
const { E10SUtils } = ChromeUtils.import(
"resource://gre/modules/E10SUtils.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
class SiteSpecificBrowserParent extends JSWindowActorParent {
receiveMessage(message) {
switch (message.name) {
case "RetargetOutOfScopeURIToBrowser":
// The content process found a URI that needs to be loaded in the main
// browser.
let triggeringPrincipal = E10SUtils.deserializePrincipal(
message.data.triggeringPrincipal
);
let referrerInfo = E10SUtils.deserializeReferrerInfo(
message.data.referrerInfo
);
let csp = E10SUtils.deserializeCSP(message.data.csp);
// Attempt to find an existing window to open it in.
let win = BrowserWindowTracker.getTopWindow();
if (win) {
win.gBrowser.selectedTab = win.gBrowser.addTab(message.data.uri, {
triggeringPrincipal,
csp,
referrerInfo,
});
} else {
let sa = Cc["@mozilla.org/array;1"].createInstance(
Ci.nsIMutableArray
);
let wuri = Cc["@mozilla.org/supports-string;1"].createInstance(
Ci.nsISupportsString
);
wuri.data = message.data.uri;
sa.appendElement(wuri);
sa.appendElement(null); // unused (bug 871161)
sa.appendElement(referrerInfo);
sa.appendElement(null); // postData
sa.appendElement(null); // allowThirdPartyFixup
sa.appendElement(null); // userContextId
sa.appendElement(null); // originPrincipal
sa.appendElement(null); // originStoragePrincipal
sa.appendElement(triggeringPrincipal);
sa.appendElement(null); // allowInheritPrincipal
sa.appendElement(csp);
Services.ww.openWindow(
null,
AppConstants.BROWSER_CHROME_URL,
null,
"chrome,dialog=no,all",
sa
);
}
break;
}
}
}

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

@ -1,908 +0,0 @@
/* 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/. */
/**
* A Site Specific Browser intends to allow the user to navigate through the
* chosen site in the SSB UI. Any attempt to load something outside the site
* should be loaded in a normal browser. In order to achieve this we have to use
* various APIs to listen for attempts to load new content and take appropriate
* action. Often this requires returning synchronous responses to method calls
* in content processes and will require data about the SSB in order to respond
* correctly. Here we implement an architecture to support that:
*
* In the main process the SiteSpecificBrowser class implements all the
* functionality involved with managing an SSB. All content processes can
* synchronously retrieve a matching SiteSpecificBrowserBase that has enough
* data about the SSB in order to be able to respond to load requests
* synchronously. To support this we give every SSB a unique ID (UUID based)
* and the appropriate data is shared via sharedData. Once created the ID can be
* used to retrieve the SiteSpecificBrowser instance in the main process or
* SiteSpecificBrowserBase instance in any content process.
*/
var EXPORTED_SYMBOLS = [
"SiteSpecificBrowserService",
"SiteSpecificBrowserBase",
"SiteSpecificBrowser",
"SSBCommandLineHandler",
];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
ManifestObtainer: "resource://gre/modules/ManifestObtainer.jsm",
ManifestProcessor: "resource://gre/modules/ManifestProcessor.jsm",
KeyValueService: "resource://gre/modules/kvstore.jsm",
OS: "resource://gre/modules/osfile.jsm",
ImageTools: "resource:///modules/ssb/ImageTools.jsm",
AppConstants: "resource://gre/modules/AppConstants.jsm",
});
if (AppConstants.platform == "win") {
ChromeUtils.defineModuleGetter(
this,
"WindowsSupport",
"resource:///modules/ssb/WindowsSupport.jsm"
);
}
/**
* A schema version for the SSB data stored in the kvstore.
*
* Version 1 has the `manifest` and `config` properties.
*/
const DATA_VERSION = 1;
/**
* The prefix used for SSB ids in the store.
*/
const SSB_STORE_PREFIX = "ssb:";
/**
* A prefix that will sort immediately after any SSB ids in the store.
*/
const SSB_STORE_LAST = "ssb;";
function uuid() {
return Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator)
.generateUUID()
.toString();
}
const sharedDataKey = id => `SiteSpecificBrowserBase:${id}`;
const storeKey = id => SSB_STORE_PREFIX + id;
/**
* Builds a lookup table for all the icons in order of size.
*/
function buildIconList(icons) {
let iconList = [];
for (let icon of icons) {
for (let sizeSpec of icon.sizes) {
let size =
sizeSpec == "any" ? Number.MAX_SAFE_INTEGER : parseInt(sizeSpec);
iconList.push({
icon,
size,
});
}
}
iconList.sort((a, b) => {
// Given that we're using MAX_SAFE_INTEGER adding a value to that would
// overflow and give odd behaviour. And we're using numbers supplied by a
// website so just compare for safety.
if (a.size < b.size) {
return -1;
}
if (a.size > b.size) {
return 1;
}
return 0;
});
return iconList;
}
const IS_MAIN_PROCESS =
Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT;
/**
* Tests whether an app manifest's scope includes the given URI.
*
* @param {nsIURI} scope the manifest's scope.
* @param {nsIURI} uri the URI to test.
* @returns {boolean} true if the uri is included in the scope.
*/
function scopeIncludes(scope, uri) {
// https://w3c.github.io/manifest/#dfn-within-scope
if (scope.prePath != uri.prePath) {
return false;
}
return uri.filePath.startsWith(scope.filePath);
}
/**
* Generates a basic app manifest for a URI.
*
* @param {nsIURI} uri the start URI for the site.
* @return {Manifest} an app manifest.
*/
function manifestForURI(uri) {
try {
let manifestURI = Services.io.newURI("/manifest.json", null, uri);
return ManifestProcessor.process({
jsonText: "{}",
manifestURL: manifestURI.spec,
docURL: uri.spec,
});
} catch (e) {
console.error(`Failed to generate a SSB manifest for ${uri.spec}.`, e);
throw e;
}
}
/**
* Creates an IconResource from the LinkHandler data.
*
* @param {object} iconData the data from the LinkHandler actor.
* @return {Promise<IconResource>} an icon resource.
*/
async function getIconResource(iconData) {
// This should be a data url so no network traffic.
let imageData = await ImageTools.loadImage(
Services.io.newURI(iconData.iconURL)
);
if (imageData.container.type == Ci.imgIContainer.TYPE_VECTOR) {
return {
src: iconData.iconURL,
purpose: ["any"],
type: imageData.type,
sizes: ["any"],
};
}
// TODO: For ico files we should find all the available sizes: Bug 1604285.
return {
src: iconData.iconURL,
purpose: ["any"],
type: imageData.type,
sizes: [`${imageData.container.width}x${imageData.container.height}`],
};
}
/**
* Generates an app manifest for a site loaded in a browser element.
*
* @param {Element} browser the browser element the site is loaded in.
* @return {Promise<Manifest>} an app manifest.
*/
async function buildManifestForBrowser(browser) {
let manifest = null;
try {
manifest = await ManifestObtainer.browserObtainManifest(browser);
} catch (e) {
// We can function without a valid manifest.
console.error(e);
}
// Reject the manifest if its scope doesn't include the current document.
if (
!manifest ||
!scopeIncludes(Services.io.newURI(manifest.scope), browser.currentURI)
) {
manifest = manifestForURI(browser.currentURI);
}
// Cache all the icons as data URIs since we can need access to them when
// the website is not loaded.
manifest.icons = (
await Promise.all(
manifest.icons.map(async icon => {
if (icon.src.startsWith("data:")) {
return icon;
}
let actor = browser.browsingContext.currentWindowGlobal.getActor(
"SiteSpecificBrowser"
);
try {
icon.src = await actor.sendQuery("LoadIcon", icon.src);
} catch (e) {
// Bad icon, drop it from the list.
return null;
}
return icon;
})
)
).filter(icon => icon);
// If the site provided no icons then try to use the normal page icons.
if (!manifest.icons.length) {
let linkHandler = browser.browsingContext.currentWindowGlobal.getActor(
"LinkHandler"
);
for (let icon of [linkHandler.icon, linkHandler.richIcon]) {
if (!icon) {
continue;
}
try {
manifest.icons.push(await getIconResource(icon));
} catch (e) {
console.warn(`Failed to load icon resource ${icon.originalURL}`, e);
}
}
}
return manifest;
}
/**
* Maintains an ID -> SSB mapping in the main process. Content processes should
* use sharedData to get a SiteSpecificBrowserBase.
*
* We do not currently expire data from here so once created an SSB instance
* lives for the lifetime of the application. The expectation is that the
* numbers of different SSBs used will be low and the memory use will also
* be low.
*/
const SSBMap = new Map();
/**
* The base contains the data about an SSB instance needed in content processes.
*
* The only data needed currently is site's `scope` which is just a URI.
*/
class SiteSpecificBrowserBase {
/**
* Creates a new SiteSpecificBrowserBase. Generally should only be called by
* code within this module.
*
* @param {nsIURI} scope the scope for the SSB.
*/
constructor(scope) {
this._scope = scope;
}
/**
* Gets the SiteSpecifcBrowserBase for an ID. If this is the main process this
* will instead return the SiteSpecificBrowser instance itself but generally
* don't call this from the main process.
*
* The returned object is not "live" and will not be updated with any
* configuration changes from the main process so do not cache this, get it
* when needed and then discard.
*
* @param {string} id the SSB ID.
* @return {SiteSpecificBrowserBase|null} the instance if it exists.
*/
static get(id) {
if (IS_MAIN_PROCESS) {
return SiteSpecificBrowser.get(id);
}
let key = sharedDataKey(id);
if (!Services.cpmm.sharedData.has(key)) {
return null;
}
let scope = Services.io.newURI(Services.cpmm.sharedData.get(key));
return new SiteSpecificBrowserBase(scope);
}
/**
* Checks whether the given URI is considered to be a part of this SSB or not.
* Any URIs that return false should be loaded in a normal browser.
*
* @param {nsIURI} uri the URI to check.
* @return {boolean} whether this SSB can load the URI.
*/
canLoad(uri) {
// Always allow loading about:blank as it is the initial page for iframes.
if (uri.spec == "about:blank") {
return true;
}
return scopeIncludes(this._scope, uri);
}
}
/**
* The SSB instance used in the main process.
*
* We maintain three pieces of data for an SSB:
*
* First is the string UUID for identification purposes.
*
* Second is an app manifest (https://w3c.github.io/manifest/). If the site does
* not provide one a basic one will be automatically generated. The intent is to
* never modify this such that it can be updated from the site when needed
* without blowing away any configuration changes a user might want to make to
* the SSB itself.
*
* Thirdly there is the SSB configuration. This includes internal data, user
* overrides for the app manifest and custom SSB extensions to the app manifest.
*
* We pass data based on these down to the SiteSpecificBrowserBase in this and
* other processes (via `_updateSharedData`).
*/
class SiteSpecificBrowser extends SiteSpecificBrowserBase {
/**
* Creates a new SiteSpecificBrowser. Generally should only be called by
* code within this module.
*
* @param {string} id the SSB's unique ID.
* @param {Manifest} manifest the app manifest for the SSB.
* @param {object?} config the SSB configuration settings.
*/
constructor(id, manifest, config = {}) {
if (!IS_MAIN_PROCESS) {
throw new Error(
"SiteSpecificBrowser instances are only available in the main process."
);
}
super(Services.io.newURI(manifest.scope));
this._id = id;
this._manifest = manifest;
this._config = Object.assign(
{
needsUpdate: true,
persisted: false,
},
config
);
// Cache the SSB for retrieval.
SSBMap.set(id, this);
this._updateSharedData();
}
/**
* Loads the SiteSpecificBrowser for the given ID.
*
* @param {string} id the SSB's unique ID.
* @param {object?} data the data to deserialize from. Do not use externally.
* @return {Promise<SiteSpecificBrowser?>} the instance if it exists.
*/
static async load(id, data = null) {
if (!IS_MAIN_PROCESS) {
throw new Error(
"SiteSpecificBrowser instances are only available in the main process."
);
}
if (SSBMap.has(id)) {
return SSBMap.get(id);
}
if (!data) {
let kvstore = await SiteSpecificBrowserService.getKVStore();
data = await kvstore.get(storeKey(id), null);
}
if (!data) {
return null;
}
try {
let parsed = JSON.parse(data);
parsed.config.persisted = true;
return new SiteSpecificBrowser(id, parsed.manifest, parsed.config);
} catch (e) {
console.error(e);
}
return null;
}
/**
* Gets the SiteSpecifcBrowser for an ID. Can only be called from the main
* process.
*
* @param {string} id the SSB ID.
* @return {SiteSpecificBrowser|null} the instance if it exists.
*/
static get(id) {
if (!IS_MAIN_PROCESS) {
throw new Error(
"SiteSpecificBrowser instances are only available in the main process."
);
}
return SSBMap.get(id);
}
/**
* Creates an SSB from a parsed app manifest.
*
* @param {Manifest} manifest the app manifest for the site.
* @return {Promise<SiteSpecificBrowser>} the generated SSB.
*/
static async createFromManifest(manifest) {
if (!SiteSpecificBrowserService.isEnabled) {
throw new Error("Site specific browsing is disabled.");
}
if (!manifest.scope.startsWith("https:")) {
throw new Error(
"Site specific browsers can only be opened for secure sites."
);
}
return new SiteSpecificBrowser(uuid(), manifest, { needsUpdate: false });
}
/**
* Creates an SSB from a site loaded in a browser element.
*
* @param {Element} browser the browser element the site is loaded in.
* @return {Promise<SiteSpecificBrowser>} the generated SSB.
*/
static async createFromBrowser(browser) {
if (!SiteSpecificBrowserService.isEnabled) {
throw new Error("Site specific browsing is disabled.");
}
if (!browser.currentURI.schemeIs("https")) {
throw new Error(
"Site specific browsers can only be opened for secure sites."
);
}
let manifest = await buildManifestForBrowser(browser);
let ssb = await SiteSpecificBrowser.createFromManifest(manifest);
if (!manifest.name) {
ssb.name = browser.contentTitle;
}
return ssb;
}
/**
* Creates an SSB from a sURI.
*
* @param {nsIURI} uri the uri to generate from.
* @return {SiteSpecificBrowser} the generated SSB.
*/
static createFromURI(uri) {
if (!SiteSpecificBrowserService.isEnabled) {
throw new Error("Site specific browsing is disabled.");
}
if (!uri.schemeIs("https")) {
throw new Error(
"Site specific browsers can only be opened for secure sites."
);
}
return new SiteSpecificBrowser(uuid(), manifestForURI(uri));
}
/**
* Caches the data needed by content processes.
*/
_updateSharedData() {
Services.ppmm.sharedData.set(sharedDataKey(this.id), this._scope.spec);
Services.ppmm.sharedData.flush();
}
/**
* Persists the data to the store if needed. When a change in configuration
* has occured call this.
*/
async _maybeSave() {
// If this SSB is persisted then update it in the data store.
if (this._config.persisted) {
let data = {
manifest: this._manifest,
config: this._config,
};
let kvstore = await SiteSpecificBrowserService.getKVStore();
await kvstore.put(storeKey(this.id), JSON.stringify(data));
}
}
/**
* Installs this SiteSpecificBrowser such that it exists for future instances
* of the application and will appear in lists of installed SSBs.
*/
async install() {
if (this._config.persisted) {
return;
}
this._config.persisted = true;
await this._maybeSave();
if (AppConstants.platform == "win") {
await WindowsSupport.install(this);
}
Services.obs.notifyObservers(
null,
"site-specific-browser-install",
this.id
);
}
/**
* Uninstalls this SiteSpecificBrowser. Undoes eveerything above. The SSB is
* still usable afterwards.
*/
async uninstall() {
if (!this._config.persisted) {
return;
}
if (AppConstants.platform == "win") {
await WindowsSupport.uninstall(this);
}
this._config.persisted = false;
let kvstore = await SiteSpecificBrowserService.getKVStore();
await kvstore.delete(storeKey(this.id));
Services.obs.notifyObservers(
null,
"site-specific-browser-uninstall",
this.id
);
}
/**
* The SSB's ID.
*/
get id() {
return this._id;
}
get name() {
if (this._config.name) {
return this._config.name;
}
if (this._manifest.name) {
return this._manifest.name;
}
return this.startURI.host;
}
set name(val) {
this._config.name = val;
this._maybeSave();
}
/**
* The default URI to load.
*/
get startURI() {
return Services.io.newURI(this._manifest.start_url);
}
/**
* Whether this SSB needs to be checked for an updated manifest.
*/
get needsUpdate() {
return this._config.needsUpdate;
}
/**
* Gets the best icon for the requested size. It may not be the exact size
* requested.
*
* Finds the smallest icon that is larger than the requested size. If no such
* icon exists returns the largest icon available. Returns null only if there
* are no icons at all.
*
* @param {Number} size the size of the desired icon in pixels.
* @return {IconResource} the icon resource for the icon.
*/
getIcon(size) {
if (!this._iconSizes) {
this._iconSizes = buildIconList(this._manifest.icons);
}
if (!this._iconSizes.length) {
return null;
}
let i = 0;
while (i < this._iconSizes.length && this._iconSizes[i].size < size) {
i++;
}
return i < this._iconSizes.length
? this._iconSizes[i].icon
: this._iconSizes[this._iconSizes.length - 1].icon;
}
/**
* Gets the best icon for the requested size. If there isn't a perfect match
* the closest match will be scaled.
*
* @param {Number} size the size of the desired icon in pixels.
* @return {string|null} a data URI for the icon.
*/
async getScaledIcon(size) {
let icon = this.getIcon(size);
if (!icon) {
return null;
}
let { container } = await ImageTools.loadImage(
Services.io.newURI(icon.src)
);
return ImageTools.scaleImage(container, size, size);
}
/**
* Updates this SSB from a new app manifest.
*
* @param {Manifest} manifest the new app manifest.
*/
async updateFromManifest(manifest) {
this._manifest = manifest;
this._iconSizes = null;
this._scope = Services.io.newURI(this._manifest.scope);
this._config.needsUpdate = false;
this._updateSharedData();
await this._maybeSave();
}
/**
* Updates this SSB from the site loaded in the browser element.
*
* @param {Element} browser the browser element.
*/
async updateFromBrowser(browser) {
let manifest = await buildManifestForBrowser(browser);
await this.updateFromManifest(manifest);
}
/**
* Launches a SSB by opening the necessary UI.
*
* @param {nsIURI?} the initial URI to load. If not provided uses the default.
*/
launch(uri = null) {
let sa = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
let idstr = Cc["@mozilla.org/supports-string;1"].createInstance(
Ci.nsISupportsString
);
idstr.data = this.id;
sa.appendElement(idstr);
if (uri) {
let uristr = Cc["@mozilla.org/supports-string;1"].createInstance(
Ci.nsISupportsString
);
uristr.data = uri.spec;
sa.appendElement(uristr);
}
let win = Services.ww.openWindow(
null,
"chrome://browser/content/ssb/ssb.html",
"_blank",
"chrome,dialog=no,all",
sa
);
if (Services.appinfo.OS == "WINNT") {
WindowsSupport.applyOSIntegration(this, win);
}
}
}
/**
* Loads the KV store for SSBs. Should always resolve with a store even if that
* means wiping whatever is currently on disk because it was unreadable.
*/
async function loadKVStore() {
let dir = OS.Path.join(OS.Constants.Path.profileDir, "ssb");
/**
* Creates an empty store. Called when we know there is an empty directory.
*/
async function createStore() {
await OS.File.makeDir(dir);
let kvstore = await KeyValueService.getOrCreate(dir, "ssb");
await kvstore.put(
"_meta",
JSON.stringify({
version: DATA_VERSION,
})
);
return kvstore;
}
// First see if anything exists.
try {
let info = await OS.File.stat(dir);
if (!info.isDir) {
await OS.File.remove(dir, { ignoreAbsent: true });
return createStore();
}
} catch (e) {
if (e.becauseNoSuchFile) {
return createStore();
}
throw e;
}
// Something exists, try to load it.
try {
let kvstore = await KeyValueService.getOrCreate(dir, "ssb");
let meta = await kvstore.get("_meta", null);
if (meta) {
let data = JSON.parse(meta);
if (data.version == DATA_VERSION) {
return kvstore;
}
console.error(`SSB store is an unexpected version ${data.version}`);
} else {
console.error("SSB store was missing meta data.");
}
// We don't know how to handle this database, re-initialize it.
await kvstore.clear();
await kvstore.put(
"_meta",
JSON.stringify({
version: DATA_VERSION,
})
);
return kvstore;
} catch (e) {
console.error(e);
// Something is very wrong. Wipe all our data and start again.
await OS.File.removeDir(dir);
return createStore();
}
}
const SiteSpecificBrowserService = {
kvstore: null,
/**
* Returns a promise that resolves to the KV store for SSBs.
*/
getKVStore() {
if (!this.kvstore) {
this.kvstore = loadKVStore();
}
return this.kvstore;
},
/**
* Checks if OS integration is enabled. This will affect whether installs and
* uninstalls have effects on the OS itself amongst other things. Generally
* only disabled for testing.
*/
get useOSIntegration() {
if (Services.appinfo.OS != "WINNT") {
return false;
}
return Services.prefs.getBoolPref("browser.ssb.osintegration", true);
},
/**
* Returns a promise that resolves to an array of all of the installed SSBs.
*/
async list() {
let kvstore = await this.getKVStore();
let list = await kvstore.enumerate(SSB_STORE_PREFIX, SSB_STORE_LAST);
return Promise.all(
Array.from(list).map(({ key: id, value: data }) =>
SiteSpecificBrowser.load(id.substring(SSB_STORE_PREFIX.length), data)
)
);
},
};
XPCOMUtils.defineLazyPreferenceGetter(
SiteSpecificBrowserService,
"isEnabled",
"browser.ssb.enabled",
false
);
async function startSSB(id) {
// Loading the SSB is async. Until that completes and launches we will
// be without an open window and the platform will not continue startup
// in that case. Flag that a window is coming.
Services.startup.enterLastWindowClosingSurvivalArea();
// Whatever happens we must exitLastWindowClosingSurvivalArea when done.
try {
let ssb = await SiteSpecificBrowser.load(id);
if (ssb) {
ssb.launch();
} else {
dump(`No SSB installed as ID ${id}\n`);
}
} finally {
Services.startup.exitLastWindowClosingSurvivalArea();
}
}
class SSBCommandLineHandler {
/* nsICommandLineHandler */
handle(cmdLine) {
if (!SiteSpecificBrowserService.isEnabled) {
return;
}
let site = cmdLine.handleFlagWithParam("ssb", false);
if (site) {
cmdLine.preventDefault = true;
try {
let fixupInfo = Services.uriFixup.getFixupURIInfo(
site,
Services.uriFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS
);
let uri = fixupInfo.preferredURI;
if (!uri) {
dump(`Unable to parse '${site}' as a URI.\n`);
return;
}
if (fixupInfo.fixupChangedProtocol && uri.schemeIs("http")) {
uri = uri
.mutate()
.setScheme("https")
.finalize();
}
let ssb = SiteSpecificBrowser.createFromURI(uri);
ssb.launch();
} catch (e) {
dump(`Unable to parse '${site}' as a URI: ${e}\n`);
}
return;
}
let id = cmdLine.handleFlagWithParam("start-ssb", false);
if (id) {
cmdLine.preventDefault = true;
startSSB(id);
}
}
get helpInfo() {
return " --ssb <uri> Open a site specific browser for <uri>.\n";
}
}
SSBCommandLineHandler.prototype.QueryInterface = ChromeUtils.generateQI([
"nsICommandLineHandler",
]);

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

@ -1,160 +0,0 @@
/* 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/. */
var EXPORTED_SYMBOLS = ["WindowsSupport"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { SiteSpecificBrowserService } = ChromeUtils.import(
"resource:///modules/SiteSpecificBrowserService.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
OS: "resource://gre/modules/osfile.jsm",
ImageTools: "resource:///modules/ssb/ImageTools.jsm",
});
const shellService = Cc["@mozilla.org/browser/shell-service;1"].getService(
Ci.nsIWindowsShellService
);
const uiUtils = Cc["@mozilla.org/windows-ui-utils;1"].getService(
Ci.nsIWindowsUIUtils
);
const taskbar = Cc["@mozilla.org/windows-taskbar;1"].getService(
Ci.nsIWinTaskbar
);
const File = Components.Constructor(
"@mozilla.org/file/local;1",
Ci.nsIFile,
"initWithPath"
);
function buildGroupId(id) {
try {
return `${taskbar.defaultGroupId}.ssb.${id}`;
} catch (e) {
return `Firefox.ssb.${id}`;
}
}
const WindowsSupport = {
/**
* Installs an SSB by creating a shortcut to launch it on the user's desktop.
*
* @param {SiteSpecificBrowser} ssb the SSB to install.
*/
async install(ssb) {
if (!SiteSpecificBrowserService.useOSIntegration) {
return;
}
let dir = OS.Path.join(OS.Constants.Path.profileDir, "ssb", ssb.id);
await OS.File.makeDir(dir, {
from: OS.Constants.Path.profileDir,
ignoreExisting: true,
});
let iconFile = new File(OS.Path.join(dir, "icon.ico"));
// We should be embedding multiple icon sizes, but the current icon encoder
// does not support this. For now just embed a sensible size.
let icon = ssb.getIcon(128);
if (icon) {
let { container } = await ImageTools.loadImage(
Services.io.newURI(icon.src)
);
ImageTools.saveIcon(container, 128, 128, iconFile);
} else {
// TODO use a default icon file.
iconFile = null;
}
let desktop = Services.dirsvc.get("Desk", Ci.nsIFile);
let link = OS.Path.join(desktop.path, `${ssb.name}.lnk`);
shellService.createShortcut(
Services.dirsvc.get("XREExeF", Ci.nsIFile),
["-profile", OS.Constants.Path.profileDir, "-start-ssb", ssb.id],
ssb.name,
iconFile,
buildGroupId(ssb.id),
new File(link)
);
},
/**
* Uninstalls an SSB by deleting its shortcut from the user's desktop.
*
* @param {SiteSpecificBrowser} ssb the SSB to uninstall.
*/
async uninstall(ssb) {
if (!SiteSpecificBrowserService.useOSIntegration) {
return;
}
let desktop = Services.dirsvc.get("Desk", Ci.nsIFile);
let link = OS.Path.join(desktop.path, `${ssb.name}.lnk`);
try {
await OS.File.remove(link);
} catch (e) {
console.error(e);
}
let dir = OS.Path.join(OS.Constants.Path.profileDir, "ssb", ssb.id);
try {
await OS.File.removeDir(dir, {
ignoreAbsent: true,
ignorePermissions: true,
});
} catch (e) {
console.error(e);
}
},
/**
* Applies the necessary OS integration to an open SSB.
*
* Sets the window icon based on the available icons.
*
* @param {SiteSpecificBrowser} ssb the SSB.
* @param {DOMWindow} window the window showing the SSB.
*/
async applyOSIntegration(ssb, window) {
const getIcon = async size => {
let icon = ssb.getIcon(size);
if (!icon) {
return null;
}
try {
let image = await ImageTools.loadImage(Services.io.newURI(icon.src));
return image.container;
} catch (e) {
console.error(e);
return null;
}
};
if (!SiteSpecificBrowserService.useOSIntegration) {
return;
}
let icons = await Promise.all([
getIcon(uiUtils.systemSmallIconSize),
getIcon(uiUtils.systemLargeIconSize),
]);
if (icons[0] || icons[1]) {
uiUtils.setWindowIcon(window, icons[0], icons[1]);
}
taskbar.setGroupIdForWindow(window, buildGroupId(ssb.id));
},
};

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

@ -1,19 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
Classes = [
{
'cid': '{6344f783-c893-4db8-91ec-7d43a46bd6f4}',
'contract_ids': [
'@mozilla.org/browser/ssb/clh;1',
],
'jsm': 'resource:///modules/SiteSpecificBrowserService.jsm',
'constructor': 'SSBCommandLineHandler',
'categories': {
'command-line-handler': 'e-ssb',
},
},
]

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

@ -1,8 +0,0 @@
# 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/.
browser.jar:
content/browser/ssb/ssb.html
content/browser/ssb/ssb.js
content/browser/ssb/ssb.css

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

@ -1,14 +0,0 @@
/* 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/. */
html,
body,
#browser-container,
#browser {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}

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

@ -1,17 +0,0 @@
<!-- 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/. -->
<!DOCTYPE html>
<html width="800" height="600">
<head>
<link rel="stylesheet" href="chrome://global/skin/global.css">
<link rel="stylesheet" href="ssb.css">
<title id="title"></title>
</head>
<body>
<div id="browser-container"></div>
<script type="text/javascript" src="ssb.js"></script>
</body>
</html>

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

@ -1,269 +0,0 @@
/* 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/. */
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
Services: "resource://gre/modules/Services.jsm",
SiteSpecificBrowser: "resource:///modules/SiteSpecificBrowserService.jsm",
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
WindowsSupport: "resource:///modules/ssb/WindowsSupport.jsm",
});
XPCOMUtils.defineLazyScriptGetter(
this,
"PrintUtils",
"chrome://global/content/printUtils.js"
);
let gSSBBrowser = null;
var gSSB = null;
function init() {
gSSB = SiteSpecificBrowser.get(window.arguments[0]);
let uri = gSSB.startURI;
if (window.arguments.length > 1) {
uri = Services.io.newURI(window.arguments[1]);
}
window.browserDOMWindow = new BrowserDOMWindow();
gSSBBrowser = document.createXULElement("browser");
gSSBBrowser.setAttribute("id", "browser");
gSSBBrowser.setAttribute("type", "content");
gSSBBrowser.setAttribute("remote", "true");
gSSBBrowser.setAttribute("nodefaultsrc", "true");
document.getElementById("browser-container").appendChild(gSSBBrowser);
// Give our actor the SSB's ID.
let actor = gSSBBrowser.browsingContext.currentWindowGlobal.getActor(
"SiteSpecificBrowser"
);
actor.sendAsyncMessage("SetSSB", gSSB.id);
gSSBBrowser.addProgressListener(
new ProgressListener(),
Ci.nsIWebProgress.NOTIFY_STATE_ALL
);
gSSBBrowser.src = uri.spec;
document.getElementById("title").textContent = gSSB.name;
}
class ProgressListener {
constructor() {
this.isInitial = true;
}
/**
* Called when the load state changes
*
* @param {nsIWebProgress} webProgress
* @param {nsIRequest} request
* @param {Number} state
* @param {Number} status
*/
async onStateChange(webProgress, request, state, status) {
if (!webProgress.isTopLevel) {
return;
}
let final =
Ci.nsIWebProgressListener.STATE_IS_WINDOW +
Ci.nsIWebProgressListener.STATE_STOP;
if ((state & final) != final) {
return;
}
// Load complete. Does the SSB need an update?
let { isInitial } = this;
this.isInitial = false;
if (isInitial && gSSB.needsUpdate) {
await gSSB.updateFromBrowser(gSSBBrowser);
if (Services.appinfo.OS == "WINNT") {
WindowsSupport.applyOSIntegration(gSSB, window);
}
}
// So the testing harness knows when the ssb is properly initialized.
let event = new CustomEvent("SSBLoad");
gSSBBrowser.dispatchEvent(event);
}
}
ProgressListener.prototype.QueryInterface = ChromeUtils.generateQI([
"nsIWebProgressListener",
"nsISupportsWeakReference",
]);
class BrowserDOMWindow {
/**
* Called when a page in the main process needs a new window to display a new
* page in.
*
* @param {nsIURI?} uri
* @param {nsIOpenWindowInfo} openWindowInfo
* @param {Number} where
* @param {Number} flags
* @param {nsIPrincipal} triggeringPrincipal
* @param {nsIContentSecurityPolicy?} csp
* @return {BrowsingContext} the BrowsingContext the URI should be loaded in.
*/
createContentWindow(
uri,
openWindowInfo,
where,
flags,
triggeringPrincipal,
csp
) {
console.error(
"createContentWindow should never be called from a remote browser"
);
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
/**
* Called from a page in the main process to open a new URI.
*
* @param {nsIURI} uri
* @param {nsIOpenWindowInfo} openWindowInfo
* @param {Number} where
* @param {Number} flags
* @param {nsIPrincipal} triggeringPrincipal
* @param {nsIContentSecurityPolicy?} csp
* @return {BrowsingContext} the BrowsingContext the URI should be loaded in.
*/
openURI(uri, openWindowInfo, where, flags, triggeringPrincipal, csp) {
console.error("openURI should never be called from a remote browser");
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
/**
* Finds a new frame to load some content in.
*
* @param {nsIURI?} uri
* @param {nsIOpenURIInFrameParams} params
* @param {Number} where
* @param {Number} flags
* @param {string} name
* @param {boolean} shouldOpen should the load start or not.
* @return {Element} the frame element the URI should be loaded in.
*/
getContentWindowOrOpenURIInFrame(
uri,
params,
where,
flags,
name,
shouldOpen
) {
// It's been determined that this load needs to happen in a new frame.
// Either onBeforeLinkTraversal set this correctly or this is the result
// of a window.open call.
if (where == Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER) {
return PrintUtils.startPrintWindow(
"window_print",
params.openWindowInfo.parent,
{ openWindowInfo: params.openWindowInfo }
);
}
// If this ssb can load the url then just load it internally.
if (gSSB.canLoad(uri)) {
return gSSBBrowser;
}
// Try and find a browser window to open in.
let win = BrowserWindowTracker.getTopWindow({
private: params.isPrivate,
allowPopups: false,
});
if (win) {
// Just hand off to the window's handler
win.focus();
return win.browserDOMWindow.openURIInFrame(
shouldOpen ? uri : null,
params,
where,
flags,
name
);
}
// We need to open a new browser window and a tab in it. That's an
// asychronous operation but luckily if we return null here the platform
// handles doing that for us.
return null;
}
/**
* Gets an nsFrameLoaderOwner to load some new content in.
*
* @param {nsIURI?} uri
* @param {nsIOpenURIInFrameParams} params
* @param {Number} where
* @param {Number} flags
* @param {string} name
* @return {Element} the frame element the URI should be loaded in.
*/
createContentWindowInFrame(uri, params, where, flags, name) {
return this.getContentWindowOrOpenURIInFrame(
uri,
params,
where,
flags,
name,
false
);
}
/**
* Create a new nsFrameLoaderOwner and load some content into it.
*
* @param {nsIURI} uri
* @param {nsIOpenURIInFrameParams} params
* @param {Number} where
* @param {Number} flags
* @param {string} name
* @return {Element} the frame element the URI is loading in.
*/
openURIInFrame(uri, params, where, flags, name) {
return this.getContentWindowOrOpenURIInFrame(
uri,
params,
where,
flags,
name,
true
);
}
canClose() {
/* globals docShell */
for (let i = 0; i < docShell.childCount; i++) {
let childShell = docShell.getChildAt(i).QueryInterface(Ci.nsIDocShell);
let { contentViewer } = childShell;
if (contentViewer && !contentViewer.permitUnload()) {
return false;
}
}
return true;
}
get tabCount() {
return 1;
}
}
BrowserDOMWindow.prototype.QueryInterface = ChromeUtils.generateQI([
"nsIBrowserDOMWindow",
]);
window.addEventListener("DOMContentLoaded", init, true);

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

@ -1,31 +0,0 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
JAR_MANIFESTS += ["content/jar.mn"]
BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.ini"]
XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.ini"]
XPCOM_MANIFESTS += [
"components.conf",
]
EXTRA_JS_MODULES += [
"SiteSpecificBrowserService.jsm",
]
EXTRA_JS_MODULES.ssb += [
"ImageTools.jsm",
]
FINAL_TARGET_FILES.actors += [
"SiteSpecificBrowserChild.jsm",
"SiteSpecificBrowserParent.jsm",
]
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
EXTRA_JS_MODULES.ssb += [
"WindowsSupport.jsm",
]

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

@ -1,24 +0,0 @@
[DEFAULT]
support-files =
head.js
test_page.html
empty_page.html
prefs =
dom.manfiest.enabled=true
browser.ssb.enabled=true
browser.ssb.osintegration=false
[browser_ssb_direct.js]
[browser_ssb_lasttab.js]
[browser_ssb_list_menu.js]
[browser_ssb_manifest_scope.js]
support-files =
site1/*
site2/*
[browser_ssb_menu.js]
[browser_ssb_newtab.js]
[browser_ssb_newwindow.js]
[browser_ssb_open.js]
[browser_ssb_windowlocation.js]
[browser_ssb_windowopen.js]
skip-if = true # It is unclear what we want to do here.

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

@ -1,66 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
async function testDirectLoad(target, checker) {
let ssb = await openSSB(gHttpsTestRoot + "test_page.html#" + target);
let promise = checker(ssb);
await BrowserTestUtils.synthesizeMouseAtCenter(
"#direct",
{},
getBrowser(ssb)
);
await promise;
await BrowserTestUtils.closeWindow(ssb);
}
// A link that should load inside the ssb
add_task(async function local() {
await testDirectLoad(gHttpsTestRoot + "empty_page.html", async ssb => {
try {
await expectSSBLoad(ssb);
Assert.equal(
getBrowser(ssb).currentURI.spec,
gHttpsTestRoot + "empty_page.html",
"Should have loaded the right uri."
);
} catch (e) {
// Any error will already have logged a failure.
}
});
});
// A link to an insecure site should load outside the ssb
add_task(async function insecure() {
await testDirectLoad(gHttpTestRoot + "empty_page.html", async ssb => {
try {
let tab = await expectTabLoad(ssb);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
gHttpTestRoot + "empty_page.html",
"Should have loaded the right uri."
);
BrowserTestUtils.removeTab(tab);
} catch (e) {
// Any error will already have logged a failure.
}
});
});
// A link to a different host should load outside the ssb
add_task(async function external() {
await testDirectLoad(gHttpsOtherRoot + "empty_page.html", async ssb => {
try {
let tab = await expectTabLoad(ssb);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
gHttpsOtherRoot + "empty_page.html",
"Should have loaded the right uri."
);
BrowserTestUtils.removeTab(tab);
} catch (e) {
// Any error will already have logged a failure.
}
});
});

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

@ -1,39 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Opening from the last browser tab should not close the window.
add_task(async () => {
let win = await BrowserTestUtils.openNewBrowserWindow();
let windowClosed = false;
BrowserTestUtils.windowClosed(win).then(() => {
windowClosed = true;
});
Assert.equal(win.gBrowser.tabs.length, 1, "Should be only one tab.");
let tab = win.gBrowser.selectedTab;
BrowserTestUtils.loadURI(
win.gBrowser.selectedBrowser,
gHttpsTestRoot + "test_page.html"
);
await BrowserTestUtils.browserLoaded(
win.gBrowser.selectedBrowser,
true,
gHttpsTestRoot + "test_page.html"
);
let ssbwin = await openSSBFromBrowserWindow(win);
Assert.equal(win.gBrowser.tabs.length, 1, "Should still be only one tab.");
Assert.notEqual(tab, win.gBrowser.selectedTab, "Should be a new tab.");
Assert.equal(
getBrowser(ssbwin).currentURI.spec,
gHttpsTestRoot + "test_page.html"
);
await getSSB(ssbwin).uninstall();
Assert.ok(!windowClosed, "Should not have seen the window close.");
await BrowserTestUtils.closeWindow(ssbwin);
await BrowserTestUtils.closeWindow(win);
});

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

@ -1,98 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Check that the menu is updated correctly and can be used to launch an ssb.
add_task(async () => {
let win = await BrowserTestUtils.openNewBrowserWindow();
let button = win.document.getElementById("appMenu-ssb-button");
Assert.equal(button, null, "Button should be not be available.");
Assert.equal(
win.document.querySelector("#appMenu-SSBView .panel-subview-body"),
null,
"Panel should not be available"
);
let ssb = await SiteSpecificBrowser.createFromURI(
Services.io.newURI(gHttpsTestRoot)
);
button = win.document.getElementById("appMenu-ssb-button");
Assert.equal(button, null, "Button should be not be available.");
Assert.equal(
win.document.querySelector("#appMenu-SSBView .panel-subview-body"),
null,
"Panel should not be available"
);
await ssb.install();
button = win.document.getElementById("appMenu-ssb-button");
// Button should still be unavailable, we don't populate the list until it is
// first opened.
Assert.equal(button, null, "Button should be not be available.");
Assert.equal(
win.document.querySelector("#appMenu-SSBView .panel-subview-body"),
null,
"Panel should not be available"
);
let appMenuOpened = BrowserTestUtils.waitForEvent(
win.document.getElementById("appMenu-popup"),
"popupshown"
);
EventUtils.synthesizeMouseAtCenter(
win.document.getElementById("PanelUI-menu-button"),
{},
win
);
await appMenuOpened;
button = win.document.getElementById("appMenu-ssb-button");
await BrowserTestUtils.waitForAttributeRemoval("hidden", button);
Assert.ok(!button.hidden, "Button should be visible.");
EventUtils.synthesizeMouseAtCenter(
win.document.getElementById("appMenu-ssb-button"),
{},
win
);
let panelShown = BrowserTestUtils.waitForEvent(
win.document.getElementById("appMenu-SSBView"),
"ViewShown"
);
let panel = win.document.querySelector(
"#appMenu-SSBView .panel-subview-body"
);
await panelShown;
Assert.notEqual(
panel.firstElementChild,
null,
"Should be something in the list."
);
Assert.equal(
panel.firstElementChild.id,
"ssb-button-" + ssb.id,
"Should have the right ID."
);
let ssbOpened = waitForSSB();
EventUtils.synthesizeMouseAtCenter(panel.firstElementChild, {}, win);
let ssbWin = await ssbOpened;
Assert.equal(getBrowser(ssbWin).currentURI.spec, gHttpsTestRoot);
await BrowserTestUtils.closeWindow(ssbWin);
await ssb.uninstall();
// Menu will be dynamically updating at this point.
Assert.ok(button.hidden, "Should be no installs.");
Assert.equal(panel.firstElementChild, null, "Should be nothing in the list.");
await BrowserTestUtils.closeWindow(win);
});

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

@ -1,83 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Enable web manifest processing.
Services.prefs.setBoolPref("dom.manifest.enabled", true);
// Check that a site's manifest affects the scope of a ssb.
function build_task(page, linkId, external, preload) {
let expectedTarget = linkId + "/final.html";
add_task(async () => {
let ssbwin;
info(`Loading ${page} (${preload})`);
if (preload) {
// Loading via a browser will initialize the SSB with the correct manifest.
await BrowserTestUtils.openNewForegroundTab({
gBrowser,
url: gHttpsTestRoot + page,
});
ssbwin = await openSSBFromBrowserWindow();
} else {
// The manifest will be loaded once the initial page loads.
ssbwin = await openSSB(gHttpsTestRoot + page);
}
let promise;
if (external) {
promise = expectTabLoad(ssbwin).then(tab => {
Assert.equal(
tab.linkedBrowser.currentURI.spec,
gHttpsTestRoot + expectedTarget,
"Should have loaded the right uri."
);
BrowserTestUtils.removeTab(tab);
});
} else {
promise = expectSSBLoad(ssbwin).then(() => {
Assert.equal(
getBrowser(ssbwin).currentURI.spec,
gHttpsTestRoot + expectedTarget,
"Should have loaded the right uri."
);
});
}
info(`Clicking #${linkId}`);
await BrowserTestUtils.synthesizeMouseAtCenter(
`#${linkId}`,
{},
getBrowser(ssbwin)
);
await promise;
await getSSB(ssbwin).uninstall();
await BrowserTestUtils.closeWindow(ssbwin);
});
}
function make_all_tasks(preload) {
/**
* Arguments are:
*
* * Page to load.
* * Link ID to click in the page.
* * Is that link expected to point to an external site (i.e. should be retargeted).
* * true to create the SSB from a loaded browser, false to create it from a URL.
* The latter implies that the manifest won't initially be available.
*/
build_task("site1/simple.html", "site1", false, preload);
build_task("site1/simple.html", "site2", true, preload);
build_task("site1/empty.html", "site1", false, preload);
build_task("site1/empty.html", "site2", true, preload);
build_task("site1/allhost.html", "site1", false, preload);
build_task("site1/allhost.html", "site2", false, preload);
build_task("site1/bad.html", "site1", false, preload);
build_task("site1/bad.html", "site2", true, preload);
}
make_all_tasks(true);
make_all_tasks(false);

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

@ -1,38 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Check that it is correctly enabled/disabled based on the displaying page.
add_task(async () => {
let tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
url: gHttpTestRoot + "test_page.html",
});
// Must open the panel before the item gets added.
let pageActionButton = document.getElementById("pageActionButton");
let panel = document.getElementById("pageActionPanel");
let popupShown = BrowserTestUtils.waitForEvent(panel, "popupshown");
EventUtils.synthesizeMouseAtCenter(pageActionButton, {}, window);
await popupShown;
let popupHidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
panel.hidePopup();
await popupHidden;
Assert.ok(
document.getElementById("pageAction-panel-launchSSB").disabled,
"Menu should be disabled for a http: page."
);
let uri = gHttpsTestRoot + "test_page.html";
BrowserTestUtils.loadURI(tab.linkedBrowser, uri);
await BrowserTestUtils.browserLoaded(tab.linkedBrowser, true, uri);
Assert.ok(
!document.getElementById("pageAction-panel-launchSSB").disabled,
"Menu should not be disabled for a https: page."
);
BrowserTestUtils.removeTab(tab);
});

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

@ -1,66 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
async function testNewTabLoad(target, checker) {
let ssb = await openSSB(gHttpsTestRoot + "test_page.html#" + target);
let promise = checker(ssb);
await BrowserTestUtils.synthesizeMouseAtCenter(
"#new-tab",
{},
getBrowser(ssb)
);
await promise;
await BrowserTestUtils.closeWindow(ssb);
}
// A link that should load inside the ssb
add_task(async function local() {
await testNewTabLoad(gHttpsTestRoot + "empty_page.html", async ssb => {
try {
await expectSSBLoad(ssb);
Assert.equal(
getBrowser(ssb).currentURI.spec,
gHttpsTestRoot + "empty_page.html",
"Should have loaded the right uri."
);
} catch (e) {
// Any error will already have logged a failure.
}
});
});
// A link to an insecure site should load outside the ssb
add_task(async function insecure() {
await testNewTabLoad(gHttpTestRoot + "empty_page.html", async ssb => {
try {
let tab = await expectTabLoad(ssb);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
gHttpTestRoot + "empty_page.html",
"Should have loaded the right uri."
);
BrowserTestUtils.removeTab(tab);
} catch (e) {
// Any error will already have logged a failure.
}
});
});
// A link to a different host should load outside the ssb
add_task(async function external() {
await testNewTabLoad(gHttpsOtherRoot + "empty_page.html", async ssb => {
try {
let tab = await expectTabLoad(ssb);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
gHttpsOtherRoot + "empty_page.html",
"Should have loaded the right uri."
);
BrowserTestUtils.removeTab(tab);
} catch (e) {
// Any error will already have logged a failure.
}
});
});

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

@ -1,66 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
async function testNewWindowLoad(target, checker) {
let ssb = await openSSB(gHttpsTestRoot + "test_page.html#" + target);
let promise = checker(ssb);
await BrowserTestUtils.synthesizeMouseAtCenter(
"#new-window",
{},
getBrowser(ssb)
);
await promise;
await BrowserTestUtils.closeWindow(ssb);
}
// A link that should load inside the ssb
add_task(async function local() {
await testNewWindowLoad(gHttpsTestRoot + "empty_page.html", async ssb => {
try {
await expectSSBLoad(ssb);
Assert.equal(
getBrowser(ssb).currentURI.spec,
gHttpsTestRoot + "empty_page.html",
"Should have loaded the right uri."
);
} catch (e) {
// Any error will already have logged a failure.
}
});
});
// A link to an insecure site should load outside the ssb
add_task(async function insecure() {
await testNewWindowLoad(gHttpTestRoot + "empty_page.html", async ssb => {
try {
let tab = await expectTabLoad(ssb);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
gHttpTestRoot + "empty_page.html",
"Should have loaded the right uri."
);
BrowserTestUtils.removeTab(tab);
} catch (e) {
// Any error will already have logged a failure.
}
});
});
// A link to a different host should load outside the ssb
add_task(async function external() {
await testNewWindowLoad(gHttpsOtherRoot + "empty_page.html", async ssb => {
try {
let tab = await expectTabLoad(ssb);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
gHttpsOtherRoot + "empty_page.html",
"Should have loaded the right uri."
);
BrowserTestUtils.removeTab(tab);
} catch (e) {
// Any error will already have logged a failure.
}
});
});

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

@ -1,25 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Verify that clicking the menu button opens an ssb.
add_task(async () => {
let tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
url: gHttpsTestRoot + "test_page.html",
});
let ssbwin = await openSSBFromBrowserWindow();
Assert.equal(
getBrowser(ssbwin).currentURI.spec,
gHttpsTestRoot + "test_page.html"
);
Assert.equal(tab.parentNode, null, "The tab should have been closed");
let ssb = getSSB(ssbwin);
// This title comes from the test_page.html title tag as there is no manifest.
Assert.equal(ssb.name, "Test site", "The name should be correct.");
await ssb.uninstall();
await BrowserTestUtils.closeWindow(ssbwin);
});

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

@ -1,72 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
async function testWindowLocationLoad(target, checker) {
let ssb = await openSSB(gHttpsTestRoot + "test_page.html#" + target);
let promise = checker(ssb);
await BrowserTestUtils.synthesizeMouseAtCenter(
"#window-location",
{},
getBrowser(ssb)
);
await promise;
await BrowserTestUtils.closeWindow(ssb);
}
// A link that should load inside the ssb
add_task(async function local() {
await testWindowLocationLoad(
gHttpsTestRoot + "empty_page.html",
async ssb => {
try {
await expectSSBLoad(ssb);
Assert.equal(
getBrowser(ssb).currentURI.spec,
gHttpsTestRoot + "empty_page.html",
"Should have loaded the right uri."
);
} catch (e) {
// Any error will already have logged a failure.
}
}
);
});
// A link to an insecure site should load outside the ssb
add_task(async function insecure() {
await testWindowLocationLoad(gHttpTestRoot + "empty_page.html", async ssb => {
try {
let tab = await expectTabLoad(ssb);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
gHttpTestRoot + "empty_page.html",
"Should have loaded the right uri."
);
BrowserTestUtils.removeTab(tab);
} catch (e) {
// Any error will already have logged a failure.
}
});
});
// A link to a different host should load outside the ssb
add_task(async function external() {
await testWindowLocationLoad(
gHttpsOtherRoot + "empty_page.html",
async ssb => {
try {
let tab = await expectTabLoad(ssb);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
gHttpsOtherRoot + "empty_page.html",
"Should have loaded the right uri."
);
BrowserTestUtils.removeTab(tab);
} catch (e) {
// Any error will already have logged a failure.
}
}
);
});

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

@ -1,66 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
async function testWindowOpenLoad(target, checker) {
let ssb = await openSSB(gHttpsTestRoot + "test_page.html#" + target);
let promise = checker(ssb);
await BrowserTestUtils.synthesizeMouseAtCenter(
"#window-open",
{},
getBrowser(ssb)
);
await promise;
await BrowserTestUtils.closeWindow(ssb);
}
// A link that should load inside the ssb
add_task(async function local() {
await testWindowOpenLoad(gHttpsTestRoot + "empty_page.html", async ssb => {
try {
await expectSSBLoad(ssb);
Assert.equal(
getBrowser(ssb).currentURI.spec,
gHttpsTestRoot + "empty_page.html",
"Should have loaded the right uri."
);
} catch (e) {
// Any error will already have logged a failure.
}
});
});
// A link to an insecure site should load outside the ssb
add_task(async function insecure() {
await testWindowOpenLoad(gHttpTestRoot + "empty_page.html", async ssb => {
try {
let tab = await expectTabLoad(ssb);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
gHttpTestRoot + "empty_page.html",
"Should have loaded the right uri."
);
BrowserTestUtils.removeTab(tab);
} catch (e) {
// Any error will already have logged a failure.
}
});
});
// A link to a different host should load outside the ssb
add_task(async function external() {
await testWindowOpenLoad(gHttpsOtherRoot + "empty_page.html", async ssb => {
try {
let tab = await expectTabLoad(ssb);
Assert.equal(
tab.linkedBrowser.currentURI.spec,
gHttpsOtherRoot + "empty_page.html",
"Should have loaded the right uri."
);
BrowserTestUtils.removeTab(tab);
} catch (e) {
// Any error will already have logged a failure.
}
});
});

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

@ -1,9 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
</body>
</html>

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

@ -1,225 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const { SiteSpecificBrowser } = ChromeUtils.import(
"resource:///modules/SiteSpecificBrowserService.jsm"
);
// An insecure site to use. SSBs cannot be insecure.
const gHttpTestRoot = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content/",
"http://example.com/"
);
// A secure site to use.
const gHttpsTestRoot = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content/",
"https://example.com/"
);
// A different secure site to use.
const gHttpsOtherRoot = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content/",
"https://example.org/"
);
// The chrome url for the SSB UI.
const SSB_WINDOW = "chrome://browser/content/ssb/ssb.html";
// Waits for an SSB window to open.
async function waitForSSB() {
let ssbwin = await BrowserTestUtils.domWindowOpened(null, async domwin => {
await BrowserTestUtils.waitForEvent(domwin, "load");
return domwin.location.toString() == SSB_WINDOW;
});
await BrowserTestUtils.waitForEvent(getBrowser(ssbwin), "SSBLoad");
return ssbwin;
}
// Directly opens an SSB for the given URI. Resolves to the SSB DOM window after
// the SSB content has loaded.
async function openSSB(uri) {
if (!(uri instanceof Ci.nsIURI)) {
uri = Services.io.newURI(uri);
}
let openPromise = waitForSSB();
let ssb = SiteSpecificBrowser.createFromURI(uri);
ssb.launch();
return openPromise;
}
// Simulates opening a SSB from the main browser window. Resolves to the SSB
// DOM window after the SSB content has loaded.
async function openSSBFromBrowserWindow(win = window) {
let doc = win.document;
let pageActionButton = doc.getElementById("pageActionButton");
EventUtils.synthesizeMouseAtCenter(pageActionButton, {}, win);
let panel = doc.getElementById("pageActionPanel");
let popupShown = BrowserTestUtils.waitForEvent(panel, "popupshown");
await popupShown;
let openItem = doc.getElementById("pageAction-panel-launchSSB");
Assert.ok(!openItem.disabled, "Open menu item should not be disabled");
Assert.ok(!openItem.hidden, "Open menu item should not be hidden");
let openPromise = waitForSSB();
EventUtils.synthesizeMouseAtCenter(openItem, {}, win);
return openPromise;
}
// Given the SSB UI DOM window gets the browser element showing the content.
function getBrowser(ssbwin) {
return ssbwin.document.getElementById("browser");
}
// Given the SSB UI DOM window gets the ssb instance showing the content.
function getSSB(ssbwin) {
return ssbwin.gSSB;
}
/**
* Waits for a load in response to an attempt to navigate from the SSB. It
* listens for new tab opens in the main window, new window opens and loads in
* the SSB itself. It returns a promise.
*
* The `where` argument is a string saying where the load is expected to
* happen, "ssb", "tab" or "window". The promise rejects if the load happens
* somewhere else and the offending new item (tab or window) get closed. When
* the load is seen in the correct location different things are returned
* depending on `where`. For "tab" the new tab is returned (it will have
* finished loading), for "window" the new window is returned (it will have
* finished loading). The "ssb" case doesn't return anything.
*
* Generally use the methods below this as they look more obvious.
*/
function expectLoadSomewhere(ssb, where, win = window) {
return new Promise((resolve, reject) => {
// Listens for a new tab opening in the main window.
const tabListener = async ({ target: tab }) => {
cleanup();
await BrowserTestUtils.browserLoaded(
tab.linkedBrowser,
true,
uri => uri != "about:blank"
);
if (where != "tab") {
Assert.ok(
false,
`Did not expect ${tab.linkedBrowser.currentURI.spec} to load in a new tab.`
);
BrowserTestUtils.removeTab(tab);
reject(new Error("Page unexpectedly loaded in a new tab."));
return;
}
Assert.ok(
true,
`${tab.linkedBrowser.currentURI.spec} loaded in a new tab as expected.`
);
resolve(tab);
};
win.gBrowser.tabContainer.addEventListener("TabOpen", tabListener);
// Listens for new top-level windows.
const winObserver = async (domwin, topic) => {
if (topic != "domwindowopened") {
return;
}
cleanup();
await BrowserTestUtils.waitForEvent(
domwin,
"load",
uri => uri != "about:blank"
);
if (where != "window") {
Assert.ok(false, `Did not expect a new ${domwin.location} to open.`);
await BrowserTestUtils.closeWindow(domwin);
reject(new Error("New window unexpectedly opened."));
return;
}
Assert.ok(true, `${domwin.location} opened as expected.`);
resolve(domwin);
};
Services.ww.registerNotification(winObserver);
const ssbListener = () => {
cleanup();
if (where != "ssb") {
Assert.ok(
false,
`Did not expect ${
getBrowser(ssb).currentURI.spec
} to load in the ssb window.`
);
reject(new Error("Page unexpectedly loaded in the ssb window."));
return;
}
Assert.ok(
true,
`${
getBrowser(ssb).currentURI.spec
} loaded in the ssb window as expected.`
);
resolve();
};
getBrowser(ssb).addEventListener("SSBLoad", ssbListener);
// Makes sure that no notifications fire after the test is done.
const cleanup = () => {
win.gBrowser.tabContainer.removeEventListener("TabOpen", tabListener);
Services.ww.unregisterNotification(winObserver);
getBrowser(ssb).removeEventListener("SSBLoad", ssbListener);
};
});
}
/**
* Waits for a load to occur in the ssb window but rejects if a new tab or
* window get opened.
*/
function expectSSBLoad(ssb, win = window) {
return expectLoadSomewhere(ssb, "ssb", win);
}
/**
* Waits for a new tab to be opened and loaded. Rejects if a new window is
* opened or the ssb loads something before. Resolves with the new loaded tab.
*/
function expectTabLoad(ssb, win = window) {
return expectLoadSomewhere(ssb, "tab", win);
}
/**
* Waits for a new window to be opened and loaded. Rejects if a new tab is
* opened or the ssb loads something before. Resolves with the new loaded
* window.
*/
function expectWindowOpen(ssb, win = window) {
return expectLoadSomewhere(ssb, "window", win);
}
add_task(async () => {
let list = await SiteSpecificBrowserService.list();
Assert.equal(
list.length,
0,
"Should be no installed SSBs at the start of a test."
);
});
registerCleanupFunction(async () => {
let list = await SiteSpecificBrowserService.list();
Assert.equal(
list.length,
0,
"Should be no installed SSBs at the end of a test."
);
});

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

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- This page links to a manifest that sets the scope to the entire host. -->
<link rel="manifest" href="allhost.json">
</head>
<body>
<p><a id="site1" href="../site1/final.html">Site 1</a></p>
<p><a id="site2" href="../site2/final.html">Site 2</a></p>
</body>
</html>

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

@ -1,3 +0,0 @@
{
"scope": "/"
}

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

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- This page links to a manifest with an invalid scope. The manifest will
just be ignored in this case. -->
<link rel="manifest" href="bad.json">
</head>
<body>
<p><a id="site1" href="../site1/final.html">Site 1</a></p>
<p><a id="site2" href="../site2/final.html">Site 2</a></p>
</body>
</html>

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

@ -1,3 +0,0 @@
{
"scope": "/foo/bar"
}

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

@ -1,14 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- This page links to an empty manifest. The default scope is the current
directory -->
<link rel="manifest" href="empty.json">
</head>
<body>
<p><a id="site1" href="../site1/final.html">Site 1</a></p>
<p><a id="site2" href="../site2/final.html">Site 2</a></p>
</body>
</html>

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

@ -1 +0,0 @@
{}

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

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p>Landing page</p>
</body>
</html>

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

@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- This page has no linked manifest. A missing manifest is treated as the same
as an empty manifest where the default scope is the current directory -->
</head>
<body>
<p><a id="site1" href="../site1/final.html">Site 1</a></p>
<p><a id="site2" href="../site2/final.html">Site 2</a></p>
</body>
</html>

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

@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p>Landing page</p>
</body>
</html>

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

@ -1,47 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Test site</title>
<meta charset="utf-8">
<script type="text/javascript">
function initialize() {
let target = window.location.hash;
if (target.length < 2) {
return;
}
target = target.substring(1);
let anchor = document.getElementById("direct");
anchor.href = target;
anchor = document.getElementById("new-tab");
anchor.href = target;
anchor = document.getElementById("new-window");
anchor.href = target;
anchor = document.getElementById("window-open");
anchor.onclick = (e) => {
window.open(target, "foo", "height=300,width=400");
e.preventDefault();
};
anchor = document.getElementById("window-location");
anchor.onclick = (e) => {
window.location = target;
e.preventDefault();
};
}
window.addEventListener("load", initialize, true);
</script>
</head>
<body>
<p><a id="direct">Direct link</a></p>
<p><a id="new-tab" target="_blank">New tab link</a></p>
<p><a id="new-window" target="foo">New window link</a></p>
<p><a id="window-open" href="#">window.open call</a></p>
<p><a id="window-location" href="#">window.location manipulation</a></p>
</body>
</html>

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

@ -1,69 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { SiteSpecificBrowser, SiteSpecificBrowserService } = ChromeUtils.import(
"resource:///modules/SiteSpecificBrowserService.jsm"
);
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const ICON16 =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAADUExURUdwTIL60tIAAAABdFJOUwBA5thmAAAAC0lEQVQIHWMgEQAAADAAAQrnSBQAAAAASUVORK5CYII=";
const ICON32 =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAQMAAABJtOi3AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAADUExURUdwTIL60tIAAAABdFJOUwBA5thmAAAAC0lEQVQIHWMY5AAAAKAAAZearVIAAAAASUVORK5CYII=";
const ICON48 =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwAQMAAABtzGvEAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAADUExURUdwTIL60tIAAAABdFJOUwBA5thmAAAADElEQVQYGWMYBVQFAAFQAAHUa/NpAAAAAElFTkSuQmCC";
const ICON96 =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgAQMAAADYVuV7AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAADUExURUdwTIL60tIAAAABdFJOUwBA5thmAAAAEUlEQVQYGWMYBaNgFIyCYQoABOAAAZ11NUsAAAAASUVORK5CYII=";
const ICON128 =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACAAQMAAAD58POIAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAADUExURUdwTIL60tIAAAABdFJOUwBA5thmAAAAGElEQVQYGWMYBaNgFIyCUTAKRsEooDMAAAiAAAE2cKqmAAAAAElFTkSuQmCC";
const ICON256 =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAAA1BMVEVHcEyC+tLSAAAAAXRSTlMAQObYZgAAAB9JREFUGBntwQENAAAAwiD7p34ON2AAAAAAAAAAAOcCIQAAAfWivQQAAAAASUVORK5CYII=";
XPCOMUtils.defineLazyModuleGetters(this, {
ManifestProcessor: "resource://gre/modules/ManifestProcessor.jsm",
KeyValueService: "resource://gre/modules/kvstore.jsm",
OS: "resource://gre/modules/osfile.jsm",
AppConstants: "resource://gre/modules/AppConstants.jsm",
});
let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].getService(
Ci.nsIXREDirProvider
);
const SSB_STORE_PREFIX = "ssb:";
const uri = spec => Services.io.newURI(spec);
const storeKey = id => SSB_STORE_PREFIX + id;
let gProfD = do_get_profile();
let gSSBData = gProfD.clone();
gSSBData.append("ssb");
Services.prefs.setBoolPref("browser.ssb.enabled", true);
Services.prefs.setBoolPref("browser.ssb.osintegration", false);
async function getKVStore() {
await OS.File.makeDir(gSSBData.path);
return KeyValueService.getOrCreate(gSSBData.path, "ssb");
}
function parseManifest(doc, manifest = {}) {
return ManifestProcessor.process({
jsonText: JSON.stringify(manifest),
manifestURL: new URL("/manifest.json", doc),
docURL: doc,
});
}
async function storeSSB(store, id, manifest, config = {}) {
return store.put(
storeKey(id),
JSON.stringify({
manifest,
config,
})
);
}

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

@ -1,8 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Basic check. There should be no SSBs in an empty profile.
add_task(async () => {
let ssbs = await SiteSpecificBrowserService.list();
Assert.equal(ssbs.length, 0);
});

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

@ -1,42 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(async function icons() {
let ssb = await SiteSpecificBrowser.createFromManifest(
parseManifest("https://www.mozilla.org/", {
icons: [
{
src: "data:b",
sizes: "24x24",
},
{
src: "data:a",
sizes: "16x16 32x32",
},
{
src: "data:c",
sizes: "any",
},
{
src: "data:d",
sizes: "128x128",
},
],
})
);
Assert.equal(ssb.getIcon(1).src, "data:a");
Assert.equal(ssb.getIcon(15).src, "data:a");
Assert.equal(ssb.getIcon(16).src, "data:a");
Assert.equal(ssb.getIcon(17).src, "data:b");
Assert.equal(ssb.getIcon(23).src, "data:b");
Assert.equal(ssb.getIcon(24).src, "data:b");
Assert.equal(ssb.getIcon(25).src, "data:a");
Assert.equal(ssb.getIcon(31).src, "data:a");
Assert.equal(ssb.getIcon(32).src, "data:a");
Assert.equal(ssb.getIcon(33).src, "data:d");
Assert.equal(ssb.getIcon(127).src, "data:d");
Assert.equal(ssb.getIcon(128).src, "data:d");
Assert.equal(ssb.getIcon(129).src, "data:c");
Assert.equal(ssb.getIcon(5000).src, "data:c");
});

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

@ -1,59 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that installing adds it to the store.
add_task(async () => {
Services.prefs.setBoolPref("browser.ssb.osintegration", true);
let ssb = await SiteSpecificBrowser.createFromManifest(
parseManifest("https://www.mozilla.org/", {
icons: [
{
src: ICON32,
sizes: "32x32",
},
{
src: ICON48,
sizes: "48x48",
},
{
src: ICON128,
sizes: "128x128",
},
],
})
);
await ssb.install();
let ssbs = await SiteSpecificBrowserService.list();
Assert.equal(ssbs.length, 1);
Assert.equal(ssbs[0], ssb);
let kvstore = await getKVStore();
Assert.ok(await kvstore.has(storeKey(ssb.id)));
// Don't want to rely on the structure too much, just make sure it looks sane.
let data = JSON.parse(await kvstore.get(`ssb:${ssb.id}`));
Assert.ok("manifest" in data);
Assert.ok("config" in data);
if (AppConstants.platform == "win") {
// Check that the shortcut is made and destroyed.
let link = Services.dirsvc.get("Desk", Ci.nsIFile);
link.append("www.mozilla.org.lnk");
Assert.ok(link.isFile());
let icon = gSSBData.clone();
icon.append(ssb.id);
icon.append("icon.ico");
Assert.ok(icon.isFile());
await ssb.uninstall();
Assert.ok(!link.exists());
let dir = gSSBData.clone();
dir.append(ssb.id);
Assert.ok(!dir.exists());
}
});

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

@ -1,88 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(async function empty_manifest() {
let ssb = await SiteSpecificBrowser.createFromManifest(
parseManifest("https://www.mozilla.org/")
);
Assert.equal(ssb.startURI.spec, "https://www.mozilla.org/");
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/")));
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/foo")));
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/bar")));
Assert.ok(!ssb.canLoad(uri("http://www.mozilla.org/")));
Assert.ok(!ssb.canLoad(uri("https://test.mozilla.org/")));
});
add_task(async function manifest_with_scope() {
let ssb = await SiteSpecificBrowser.createFromManifest(
parseManifest("https://www.mozilla.org/foo/bar", {
scope: "https://www.mozilla.org/foo",
})
);
Assert.equal(ssb.startURI.spec, "https://www.mozilla.org/foo/bar");
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/foo")));
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/foo/bar")));
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/foo/baz")));
// Note: scopes are simple path prefixes.
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/food")));
Assert.ok(!ssb.canLoad(uri("https://www.mozilla.org/")));
Assert.ok(!ssb.canLoad(uri("https://www.mozilla.org/bar")));
Assert.ok(!ssb.canLoad(uri("http://www.mozilla.org/")));
Assert.ok(!ssb.canLoad(uri("https://test.mozilla.org/")));
});
add_task(async function manifest_with_start_url() {
let ssb = await SiteSpecificBrowser.createFromManifest(
parseManifest("https://www.mozilla.org/foo/bar", {
start_url: "https://www.mozilla.org/foo/",
})
);
Assert.equal(ssb.startURI.spec, "https://www.mozilla.org/foo/");
// scope should be "https://www.mozilla.org/foo/"
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/foo/bar")));
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/foo/baz")));
Assert.ok(!ssb.canLoad(uri("https://www.mozilla.org/foo")));
Assert.ok(!ssb.canLoad(uri("https://www.mozilla.org/")));
Assert.ok(!ssb.canLoad(uri("https://www.mozilla.org/bar")));
Assert.ok(!ssb.canLoad(uri("http://www.mozilla.org/")));
Assert.ok(!ssb.canLoad(uri("https://test.mozilla.org/")));
});
add_task(async function update_manifest() {
let ssb = await SiteSpecificBrowser.createFromManifest(
parseManifest("https://www.mozilla.org/foo/bar/bas", {
start_url: "https://www.mozilla.org/foo/bar/bas",
scope: "https://www.mozilla.org/foo/bar/",
})
);
Assert.equal(ssb.startURI.spec, "https://www.mozilla.org/foo/bar/bas");
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/foo/bar/")));
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/foo/bar/foo")));
Assert.ok(!ssb.canLoad(uri("https://www.mozilla.org/foo")));
Assert.ok(!ssb.canLoad(uri("https://www.mozilla.org/foo/bar")));
await ssb.updateFromManifest(
parseManifest("https://www.mozilla.org/foo/bar/bas", {
start_url: "https://www.mozilla.org/foo/bar/bas",
scope: "https://www.mozilla.org/foo/",
})
);
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/foo/bar/")));
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/foo/bar/foo")));
Assert.ok(!ssb.canLoad(uri("https://www.mozilla.org/foo")));
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/foo/bar")));
});

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

@ -1,22 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// A database that is too new should be wiped.
add_task(async () => {
let kvstore = await getKVStore();
kvstore.put(
"_meta",
JSON.stringify({
version: 1000,
})
);
storeSSB(kvstore, "a", parseManifest("https://www.mozilla.org/"), {});
storeSSB(kvstore, "b", parseManifest("https://www.microsoft.com/"), {});
let ssbs = await SiteSpecificBrowserService.list();
Assert.equal(ssbs.length, 0);
let meta = JSON.parse(await kvstore.get("_meta"));
Assert.equal(meta.version, 1);
});

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

@ -1,31 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// Tests that uninstalling removes from the store.
add_task(async () => {
let kvstore = await getKVStore();
kvstore.put(
"_meta",
JSON.stringify({
version: 1,
})
);
storeSSB(kvstore, "a", parseManifest("https://www.mozilla.org/"), {});
let ssb = await SiteSpecificBrowser.load("a");
let ssbs = await SiteSpecificBrowserService.list();
Assert.equal(ssbs.length, 1);
Assert.equal(ssbs[0], ssb);
Assert.ok(ssb.canLoad(uri("https://www.mozilla.org/test/")));
Assert.ok(!ssb.canLoad(uri("https://www.microsoft.com/test/")));
await ssb.uninstall();
ssbs = await SiteSpecificBrowserService.list();
Assert.equal(ssbs.length, 0);
Assert.ok(!(await kvstore.has(storeKey("a"))));
});

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

@ -1,12 +0,0 @@
[DEFAULT]
head = head.js
firefox-appdir = browser
[test_empty.js]
[test_icons.js]
[test_install.js]
[test_manifest.js]
[test_too_new.js]
skip-if = tsan # Times out, bug 1674773
[test_uninstall.js]
skip-if = tsan # Times out, bug 1674773

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

@ -36,11 +36,6 @@ ChromeUtils.defineModuleGetter(
"PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"SiteSpecificBrowserService",
"resource:///modules/SiteSpecificBrowserService.jsm"
);
const ACTION_ID_BOOKMARK = "bookmark";
const ACTION_ID_PIN_TAB = "pinTab";
@ -1292,20 +1287,6 @@ if (Services.prefs.getBoolPref("identity.fxaccounts.enabled")) {
});
}
if (SiteSpecificBrowserService.isEnabled) {
gBuiltInActions.push({
id: "launchSSB",
// Hardcoded for now. Localization tracked in bug 1602528.
title: "Use This Site in App Mode",
onLocationChange(browserWindow) {
browserPageActions(browserWindow).launchSSB.updateState();
},
onCommand(event, buttonNode) {
browserPageActions(buttonNode).launchSSB.onCommand(event, buttonNode);
},
});
}
// share URL
if (AppConstants.platform == "macosx") {
gBuiltInActions.push({

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

@ -1,7 +0,0 @@
<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M15 2h-1V1a1 1 0 0 0-2 0v1h-1a1 1 0 0 0 0 2h1v1a1 1 0 0 0 2 0V4h1a1 1 0 0 0 0-2z"/>
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M13 7a1 1 0 0 0-1 1v5H3V4h5a1 1 0 0 0 0-2H2a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h11a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1z"/>
</svg>

До

Ширина:  |  Высота:  |  Размер: 610 B

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

@ -292,5 +292,4 @@
skin/classic/browser/privatebrowsing/aboutPrivateBrowsing.css (../shared/privatebrowsing/aboutPrivateBrowsing.css)
skin/classic/browser/privatebrowsing/favicon.svg (../shared/privatebrowsing/favicon.svg)
skin/classic/browser/privatebrowsing/private-browsing.svg (../shared/privatebrowsing/private-browsing.svg)
skin/classic/browser/install-ssb.svg (../shared/install-ssb.svg)
skin/classic/browser/critical.svg (../shared/icons/critical.svg)

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

@ -177,9 +177,3 @@ toolbarpaletteitem[place="palette"] > #bookmarks-menu-button,
#appMenu-library-downloads-show-button {
list-style-image: url("chrome://browser/skin/folder.svg");
}
.panel-subview-body .ssb-uninstall > .toolbarbutton-icon {
list-style-image: url("chrome://global/skin/icons/close.svg");
-moz-context-properties: fill, fill-opacity;
fill-opacity: 0;
}

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

@ -473,11 +473,6 @@
--arrowpanel-background: #fbfbfb;
}
#pageAction-panel-launchSSB,
#pageAction-urlbar-launchSSB {
list-style-image: url("chrome://browser/skin/install-ssb.svg");
}
/* URL bar and page action buttons */
#page-action-buttons {