зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1682593: Remove the site specific browser feature. r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D101860
This commit is contained in:
Родитель
383977e4ac
Коммит
2459f40c64
|
@ -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 {
|
||||
|
|
Загрузка…
Ссылка в новой задаче