зеркало из https://github.com/mozilla/gecko-dev.git
Bug 912891 - [app manager] Implement a CUSTOM host. r=harth
This commit is contained in:
Родитель
069bf2bdef
Коммит
c25e2c22e8
|
@ -206,11 +206,13 @@ DevTools.prototype = {
|
|||
* The id of the tool to show
|
||||
* @param {Toolbox.HostType} hostType
|
||||
* The type of host (bottom, window, side)
|
||||
* @param {object} hostOptions
|
||||
* Options for host specifically
|
||||
*
|
||||
* @return {Toolbox} toolbox
|
||||
* The toolbox that was opened
|
||||
*/
|
||||
showToolbox: function(target, toolId, hostType) {
|
||||
showToolbox: function(target, toolId, hostType, hostOptions) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let toolbox = this._toolboxes.get(target);
|
||||
|
@ -233,7 +235,7 @@ DevTools.prototype = {
|
|||
}
|
||||
else {
|
||||
// No toolbox for target, create one
|
||||
toolbox = new devtools.Toolbox(target, toolId, hostType);
|
||||
toolbox = new devtools.Toolbox(target, toolId, hostType, hostOptions);
|
||||
|
||||
this._toolboxes.set(target, toolbox);
|
||||
|
||||
|
|
|
@ -22,3 +22,4 @@ support-files = head.js
|
|||
[browser_toolbox_window_shortcuts.js]
|
||||
[browser_toolbox_window_title_changes.js]
|
||||
[browser_toolbox_zoom.js]
|
||||
[browser_toolbox_custom_host.js]
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function test() {
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
let temp = {}
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm", temp);
|
||||
let DevTools = temp.DevTools;
|
||||
Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm", temp);
|
||||
let LayoutHelpers = temp.LayoutHelpers;
|
||||
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm", temp);
|
||||
let devtools = temp.devtools;
|
||||
|
||||
let Toolbox = devtools.Toolbox;
|
||||
|
||||
let toolbox, iframe, target, tab;
|
||||
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
|
||||
window.addEventListener("message", onMessage);
|
||||
|
||||
iframe = document.createElement("iframe");
|
||||
document.documentElement.appendChild(iframe);
|
||||
|
||||
gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
|
||||
gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
|
||||
let options = {customIframe: iframe};
|
||||
gDevTools.showToolbox(target, null, Toolbox.HostType.CUSTOM, options)
|
||||
.then(testCustomHost, console.error)
|
||||
.then(null, console.error);
|
||||
}, true);
|
||||
|
||||
content.location = "data:text/html,test custom host";
|
||||
|
||||
function onMessage(event) {
|
||||
info("onMessage: " + event.data);
|
||||
let json = JSON.parse(event.data);
|
||||
if (json.name == "toolbox-close") {
|
||||
ok("Got the `toolbox-close` message");
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
function testCustomHost(toolbox) {
|
||||
is(toolbox.doc.defaultView.top, window, "Toolbox is included in browser.xul");
|
||||
is(toolbox.doc, iframe.contentDocument, "Toolbox is in the custom iframe");
|
||||
executeSoon(() => gBrowser.removeCurrentTab());
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
window.removeEventListener("message", onMessage);
|
||||
iframe.remove();
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ let promise = require("sdk/core/promise");
|
|||
let EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/devtools/DOMHelpers.jsm");
|
||||
|
||||
/**
|
||||
* A toolbox host represents an object that contains a toolbox (e.g. the
|
||||
|
@ -23,7 +24,8 @@ Cu.import("resource://gre/modules/Services.jsm");
|
|||
exports.Hosts = {
|
||||
"bottom": BottomHost,
|
||||
"side": SidebarHost,
|
||||
"window": WindowHost
|
||||
"window": WindowHost,
|
||||
"custom": CustomHost
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,18 +63,18 @@ BottomHost.prototype = {
|
|||
this._nbox.appendChild(this.frame);
|
||||
|
||||
let frameLoad = function() {
|
||||
this.frame.removeEventListener("DOMContentLoaded", frameLoad, true);
|
||||
this.emit("ready", this.frame);
|
||||
|
||||
deferred.resolve(this.frame);
|
||||
}.bind(this);
|
||||
|
||||
this.frame.tooltip = "aHTMLTooltip";
|
||||
this.frame.addEventListener("DOMContentLoaded", frameLoad, true);
|
||||
|
||||
// we have to load something so we can switch documents if we have to
|
||||
this.frame.setAttribute("src", "about:blank");
|
||||
|
||||
let domHelper = new DOMHelpers(this.frame.contentWindow);
|
||||
domHelper.onceDOMReady(frameLoad);
|
||||
|
||||
focusTab(this.hostTab);
|
||||
|
||||
return deferred.promise;
|
||||
|
@ -272,6 +274,59 @@ WindowHost.prototype = {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Host object for the toolbox in its own tab
|
||||
*/
|
||||
function CustomHost(hostTab, options) {
|
||||
this.frame = options.customIframe;
|
||||
this.uid = options.uid;
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
CustomHost.prototype = {
|
||||
type: "custom",
|
||||
|
||||
_sendMessageToTopWindow: function CH__sendMessageToTopWindow(msg) {
|
||||
// It's up to the custom frame owner (parent window) to honor
|
||||
// "close" or "raise" instructions.
|
||||
let topWindow = this.frame.ownerDocument.defaultView;
|
||||
let json = {name:"toolbox-" + msg, uid: this.uid}
|
||||
topWindow.postMessage(JSON.stringify(json), "*");
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new xul window to contain the toolbox.
|
||||
*/
|
||||
create: function CH_create() {
|
||||
return promise.resolve(this.frame);
|
||||
},
|
||||
|
||||
/**
|
||||
* Raise the host.
|
||||
*/
|
||||
raise: function CH_raise() {
|
||||
this._sendMessageToTopWindow("raise");
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the toolbox title.
|
||||
*/
|
||||
setTitle: function CH_setTitle(title) {
|
||||
// Not supported
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the window.
|
||||
*/
|
||||
destroy: function WH_destroy() {
|
||||
if (!this._destroyed) {
|
||||
this._destroyed = true;
|
||||
this._sendMessageToTopWindow("close");
|
||||
}
|
||||
return promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to the given tab in a browser and focus the browser window
|
||||
*/
|
||||
|
|
|
@ -19,6 +19,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/devtools/gDevTools.jsm");
|
||||
Cu.import("resource:///modules/devtools/scratchpad-manager.jsm");
|
||||
Cu.import("resource:///modules/devtools/DOMHelpers.jsm");
|
||||
|
||||
loader.lazyGetter(this, "Hosts", () => require("devtools/framework/toolbox-hosts").Hosts);
|
||||
|
||||
|
@ -55,8 +56,10 @@ loader.lazyGetter(this, "Requisition", () => {
|
|||
* Tool to select initially
|
||||
* @param {Toolbox.HostType} hostType
|
||||
* Type of host that will host the toolbox (e.g. sidebar, window)
|
||||
* @param {object} hostOptions
|
||||
* Options for host specifically
|
||||
*/
|
||||
function Toolbox(target, selectedTool, hostType) {
|
||||
function Toolbox(target, selectedTool, hostType, hostOptions) {
|
||||
this._target = target;
|
||||
this._toolPanels = new Map();
|
||||
this._telemetry = new Telemetry();
|
||||
|
@ -79,7 +82,7 @@ function Toolbox(target, selectedTool, hostType) {
|
|||
}
|
||||
this._defaultToolId = selectedTool;
|
||||
|
||||
this._host = this._createHost(hostType);
|
||||
this._host = this._createHost(hostType, hostOptions);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
|
@ -99,7 +102,8 @@ exports.Toolbox = Toolbox;
|
|||
Toolbox.HostType = {
|
||||
BOTTOM: "bottom",
|
||||
SIDE: "side",
|
||||
WINDOW: "window"
|
||||
WINDOW: "window",
|
||||
CUSTOM: "custom"
|
||||
};
|
||||
|
||||
Toolbox.prototype = {
|
||||
|
@ -187,8 +191,6 @@ Toolbox.prototype = {
|
|||
let deferred = promise.defer();
|
||||
|
||||
let domReady = () => {
|
||||
iframe.removeEventListener("DOMContentLoaded", domReady, true);
|
||||
|
||||
this.isReady = true;
|
||||
|
||||
let closeButton = this.doc.getElementById("toolbox-close");
|
||||
|
@ -211,9 +213,11 @@ Toolbox.prototype = {
|
|||
});
|
||||
};
|
||||
|
||||
iframe.addEventListener("DOMContentLoaded", domReady, true);
|
||||
iframe.setAttribute("src", this._URL);
|
||||
|
||||
let domHelper = new DOMHelpers(iframe.contentWindow);
|
||||
domHelper.onceDOMReady(domReady);
|
||||
|
||||
return deferred.promise;
|
||||
});
|
||||
},
|
||||
|
@ -387,6 +391,7 @@ Toolbox.prototype = {
|
|||
for (let type in Toolbox.HostType) {
|
||||
let position = Toolbox.HostType[type];
|
||||
if (position == this.hostType ||
|
||||
position == Toolbox.HostType.CUSTOM ||
|
||||
(!sideEnabled && position == Toolbox.HostType.SIDE)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -555,8 +560,6 @@ Toolbox.prototype = {
|
|||
vbox.appendChild(iframe);
|
||||
|
||||
let onLoad = () => {
|
||||
iframe.removeEventListener("DOMContentLoaded", onLoad, true);
|
||||
|
||||
let built = definition.build(iframe.contentWindow, this);
|
||||
promise.resolve(built).then((panel) => {
|
||||
this._toolPanels.set(id, panel);
|
||||
|
@ -566,8 +569,25 @@ Toolbox.prototype = {
|
|||
});
|
||||
};
|
||||
|
||||
iframe.addEventListener("DOMContentLoaded", onLoad, true);
|
||||
iframe.setAttribute("src", definition.url);
|
||||
|
||||
// Depending on the host, iframe.contentWindow is not always
|
||||
// defined at this moment. If it is not defined, we use an
|
||||
// event listener on the iframe DOM node. If it's defined,
|
||||
// we use the chromeEventHandler. We can't use a listener
|
||||
// on the DOM node every time because this won't work
|
||||
// if the (xul chrome) iframe is loaded in a content docshell.
|
||||
if (iframe.contentWindow) {
|
||||
let domHelper = new DOMHelpers(iframe.contentWindow);
|
||||
domHelper.onceDOMReady(onLoad);
|
||||
} else {
|
||||
let callback = () => {
|
||||
iframe.removeEventListener("DOMContentLoaded", callback);
|
||||
onLoad();
|
||||
}
|
||||
iframe.addEventListener("DOMContentLoaded", callback);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
|
@ -732,13 +752,13 @@ Toolbox.prototype = {
|
|||
* @return {Host} host
|
||||
* The created host object
|
||||
*/
|
||||
_createHost: function(hostType) {
|
||||
_createHost: function(hostType, options) {
|
||||
if (!Hosts[hostType]) {
|
||||
throw new Error("Unknown hostType: " + hostType);
|
||||
}
|
||||
|
||||
// clean up the toolbox if its window is closed
|
||||
let newHost = new Hosts[hostType](this.target.tab);
|
||||
let newHost = new Hosts[hostType](this.target.tab, options);
|
||||
newHost.on("window-closed", this.destroy);
|
||||
return newHost;
|
||||
},
|
||||
|
@ -766,7 +786,9 @@ Toolbox.prototype = {
|
|||
|
||||
this._host = newHost;
|
||||
|
||||
Services.prefs.setCharPref(this._prefs.LAST_HOST, this._host.type);
|
||||
if (this.hostType != Toolbox.HostType.CUSTOM) {
|
||||
Services.prefs.setCharPref(this._prefs.LAST_HOST, this._host.type);
|
||||
}
|
||||
|
||||
this._buildDockButtons();
|
||||
this._addKeysToWindow();
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
* 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 Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["DOMHelpers"];
|
||||
|
||||
/**
|
||||
|
@ -13,6 +17,9 @@ this.EXPORTED_SYMBOLS = ["DOMHelpers"];
|
|||
* The content window, owning the document to traverse.
|
||||
*/
|
||||
this.DOMHelpers = function DOMHelpers(aWindow) {
|
||||
if (!aWindow) {
|
||||
throw new Error("window can't be null or undefined");
|
||||
}
|
||||
this.window = aWindow;
|
||||
};
|
||||
|
||||
|
@ -120,5 +127,30 @@ DOMHelpers.prototype = {
|
|||
{
|
||||
delete this.window;
|
||||
delete this.treeWalker;
|
||||
},
|
||||
|
||||
/**
|
||||
* A simple way to be notified (once) when a window becomes
|
||||
* interactive (DOMContentLoaded).
|
||||
*
|
||||
* It is based on the chromeEventHandler. This is useful when
|
||||
* chrome iframes are loaded in content docshells (in Firefox
|
||||
* tabs for example).
|
||||
*/
|
||||
onceDOMReady: function Helpers_onLocationChange(callback) {
|
||||
let window = this.window;
|
||||
let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
let onReady = function(event) {
|
||||
if (event.target == window.document) {
|
||||
docShell.chromeEventHandler.removeEventListener("DOMContentLoaded", onReady, false);
|
||||
// If in `callback` the URL of the window is changed and a listener to DOMContentLoaded
|
||||
// is attached, the event we just received will be also be caught by the new listener.
|
||||
// We want to avoid that so we execute the callback in the next queue.
|
||||
Services.tm.mainThread.dispatch(callback, 0);
|
||||
}
|
||||
}
|
||||
docShell.chromeEventHandler.addEventListener("DOMContentLoaded", onReady, false);
|
||||
}
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче