Bug 736688 - Part 6: Add BrowserElementAPI.js, implementing loadstart, loadend, locationchange, and the window.{top,parent,frameElement} override for <iframe mozbrowser>. r=smaug

--HG--
extra : rebase_source : 7ed3e56bbf06d97a9b0127eecf87128c5389a1b1
This commit is contained in:
Justin Lebar 2012-03-28 11:36:50 -07:00
Родитель a175fc39c5
Коммит 4887e8d3aa
10 изменённых файлов: 340 добавлений и 2 удалений

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

@ -297,6 +297,8 @@
; JavaScript components
@BINPATH@/components/ConsoleAPI.manifest
@BINPATH@/components/ConsoleAPI.js
@BINPATH@/components/BrowserElementAPI.manifest
@BINPATH@/components/BrowserElementAPI.js
@BINPATH@/components/ContactManager.js
@BINPATH@/components/ContactManager.manifest
@BINPATH@/components/FeedProcessor.manifest

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

@ -289,6 +289,8 @@
; JavaScript components
@BINPATH@/components/ConsoleAPI.manifest
@BINPATH@/components/ConsoleAPI.js
@BINPATH@/components/BrowserElementAPI.manifest
@BINPATH@/components/BrowserElementAPI.js
@BINPATH@/components/FeedProcessor.manifest
@BINPATH@/components/FeedProcessor.js
@BINPATH@/components/BrowserFeeds.manifest

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

@ -294,5 +294,5 @@ nsGenericHTMLFrameElement::GetReallyIsBrowser(bool *aOut)
// Otherwise, succeed.
*aOut = true;
return true;
return NS_OK;
}

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

@ -11766,6 +11766,42 @@ nsDocShell::GetIsBrowserFrame(bool *aOut)
NS_IMETHODIMP
nsDocShell::SetIsBrowserFrame(bool aValue)
{
// Disallow transitions from browser frame to not-browser-frame. Once a
// browser frame, always a browser frame. (Otherwise, observers of
// docshell-marked-as-browser-frame would have to distinguish between
// newly-created browser frames and frames which went from true to false back
// to true.)
NS_ENSURE_STATE(!mIsBrowserFrame);
bool wasBrowserFrame = mIsBrowserFrame;
mIsBrowserFrame = aValue;
if (aValue && !wasBrowserFrame) {
nsCOMPtr<nsIObserverService> os = services::GetObserverService();
if (os) {
os->NotifyObservers(GetAsSupports(this),
"docshell-marked-as-browser-frame", NULL);
}
}
return NS_OK;
}
NS_IMETHODIMP
nsDocShell::GetContainedInBrowserFrame(bool *aOut)
{
*aOut = false;
if (mIsBrowserFrame) {
*aOut = true;
return NS_OK;
}
nsCOMPtr<nsIDocShellTreeItem> parentAsItem;
GetSameTypeParent(getter_AddRefs(parentAsItem));
nsCOMPtr<nsIDocShell> parent = do_QueryInterface(parentAsItem);
if (parent) {
return parent->GetContainedInBrowserFrame(aOut);
}
return NS_OK;
}

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

@ -72,7 +72,7 @@ interface nsIPrincipal;
interface nsIWebBrowserPrint;
interface nsIVariant;
[scriptable, uuid(DBD39C21-5788-4C68-9D97-0FCEE289BCE1)]
[scriptable, uuid(c7325422-817e-4321-957a-c0bdd764941d)]
interface nsIDocShell : nsISupports
{
/**
@ -614,4 +614,10 @@ interface nsIDocShell : nsISupports
* See also nsIMozBrowserFrame.
*/
attribute bool isBrowserFrame;
/*
* Is this docshell contained in an <iframe mozbrowser>, either directly or
* indirectly?
*/
readonly attribute bool containedInBrowserFrame;
};

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

@ -0,0 +1,283 @@
/* 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";
let Cu = Components.utils;
let Ci = Components.interfaces;
let Cc = Components.classes;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";
/**
* The BrowserElementAPI implements <iframe mozbrowser>.
*
* We detect windows and docshells contained inside <iframe mozbrowser>s and
* alter their behavior so that the page inside the iframe can't tell that it's
* framed and the page outside the iframe can observe changes within the iframe
* (e.g. loadstart/loadstart, locationchange).
*/
function BrowserElementAPI() {}
BrowserElementAPI.prototype = {
classID: Components.ID("{5d6fcab3-6c12-4db6-80fb-352df7a41602}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
/**
* The keys of this map are the set of chrome event handlers we've observed
* which contain a mozbrowser window.
*
* The values in this map are ignored.
*/
_chromeEventHandlersWatching: new WeakMap(),
/**
* The keys of this map are the set of windows we've observed that are
* directly contained in <iframe mozbrowser>s.
*
* The values in this map are ignored.
*/
_topLevelBrowserWindows: new WeakMap(),
_browserFramesPrefEnabled: function BA_browserFramesPrefEnabled() {
var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
try {
return prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
}
catch(e) {
return false;
}
},
/**
* Called on browser start, and also when we observe a change in
* the browser-frames-enabled pref.
*/
_init: function BA_init() {
if (this._initialized) {
return;
}
// If browser frames are disabled, watch the pref so we can enable
// ourselves if the pref is flipped. This is important for tests, if
// nothing else.
if (!this._browserFramesPrefEnabled()) {
var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
prefs.addObserver(BROWSER_FRAMES_ENABLED_PREF, this, /* ownsWeak = */ true);
return;
}
this._initialized = true;
this._progressListener._browserElementAPI = this;
var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
os.addObserver(this, 'content-document-global-created', /* ownsWeak = */ true);
os.addObserver(this, 'docshell-marked-as-browser-frame', /* ownsWeak = */ true);
},
/**
* Called when we observe a docshell-marked-as-browser-frame event, which
* happens when a docshell is created inside an <iframe mozbrowser>.
*
* A docshell may not be un-marked as a browser frame -- this ensures that
* this event will never fire twice for the same docshell, which guarantees
* that we'll never register duplicate listeners.
*/
_observeDocshellMarkedAsBrowserFrame: function BA_observeDocshellMarkedAsBrowserFrame(docshell) {
docshell.QueryInterface(Ci.nsIWebProgress)
.addProgressListener(this._progressListener,
Ci.nsIWebProgress.NOTIFY_LOCATION |
Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
},
/**
* Called when a content window is created. If the window is directly or
* indirectly contained in an <iframe mozbrowser>, we'll modify it.
*/
_observeContentGlobalCreated: function BA_observeContentGlobalCreated(win) {
var docshell = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
// If this window is not directly or indirectly inside an
// <iframe mozbrowser>, BrowserElementAPI does nothing to it.
if (!docshell.containedInBrowserFrame) {
return;
}
this._initBrowserWindow(win, docshell.isBrowserFrame);
// If this window is directly contained in an <iframe mozbrowser>, do some
// extra work.
if (docshell.isBrowserFrame) {
this._topLevelBrowserWindows.set(win, true);
this._initTopLevelBrowserWindow(win);
}
},
/**
* Initialize a content window which is indirectly or directly contained by
* an <iframe mozbrowser>.
*
* |isTopLevel| is true iff |win| is directly contained by an
* <iframe mozbrowser>.
*/
_initBrowserWindow: function BA_initBrowserWindow(win, isTopLevel) {
// XPCNativeWrapper.unwrap gets us the object that content sees; this is
// the object object that we must define properties on. Otherwise, the
// properties will be visible only to chrome!
var unwrappedWin = XPCNativeWrapper.unwrap(win);
Object.defineProperty(unwrappedWin, 'top', {
get: function() {
if (isTopLevel) {
return win;
}
// Call the mozbrowser-aware |top| method we presumably defined on our
// parent.
return XPCNativeWrapper.unwrap(win.parent).top;
}
});
Object.defineProperty(unwrappedWin, 'parent', {
get: function() {
if (isTopLevel) {
return win;
}
return win.parent;
}
});
Object.defineProperty(unwrappedWin, 'frameElement', {
get: function() {
if (isTopLevel) {
return null;
}
return win.frameElement;
}
});
},
/**
* Initialize a content window directly contained by an <iframe mozbrowser>.
*/
_initTopLevelBrowserWindow: function BA_initTopLevelBrowserWindow(win) {
// If we haven't seen this window's chrome event handler before, register
// listeners on it.
var chromeHandler = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.chromeEventHandler;
if (chromeHandler && !this._chromeEventHandlersWatching.has(chromeHandler)) {
this._chromeEventHandlersWatching.set(chromeHandler, true);
this._addChromeEventHandlerListeners(chromeHandler);
}
},
/**
* Add some listeners to a chrome event handler. Don't call this twice for
* the same chrome event handler or we'll get duplicate listeners!
*/
_addChromeEventHandlerListeners: function BA_addChromeEventHandlerListeners(chromeHandler) {
var browserElementAPI = this;
// Listen for DOMTitleChanged events on top-level <iframe mozbrowser>
// windows. (The chrome event handler handles
chromeHandler.addEventListener(
'DOMTitleChanged',
function(e) {
var win = e.target.defaultView;
if (browserElementAPI._topLevelBrowserWindows.has(win)) {
browserElementAPI._fireCustomEvent('titlechange', e.target.title,
win, win.frameElement);
}
},
/* useCapture = */ false,
/* wantsUntrusted = */ false);
},
/**
* Asynchronously fire a vanilla event at the given window's frame element.
* (Presumably, the window's frame element is an <iframe mozbrowser>.)
*
* We'll prepend 'mozbrowser' to the event's name.
*/
_fireEvent: function BA_fireEvent(name, win) {
// Because we're chrome, win.frameElement ignores <iframe mozbrowser>
// boundaries, as desired.
var evt = new win.Event('mozbrowser' + name);
win.setTimeout(function() { win.frameElement.dispatchEvent(evt) }, 0);
},
/**
* Like _fireEvent, but fire a customevent with the given data, instead of a
* vanilla event.
*/
_fireCustomEvent: function BA_fireCustomEvent(name, data, win) {
var evt = new win.CustomEvent('mozbrowser' + name, {detail: data});
win.setTimeout(function() { win.frameElement.dispatchEvent(evt) }, 0);
},
/**
* An nsIWebProgressListener registered on docshells directly contained in an
* <iframe mozbrowser>.
*/
_progressListener: {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
Ci.nsISupports]),
_getWindow: function(webProgress) {
return webProgress.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
},
onLocationChange: function(webProgress, request, location, flags) {
this._browserElementAPI._fireCustomEvent('locationchange', location.spec,
this._getWindow(webProgress));
},
onStateChange: function(webProgress, request, stateFlags, status) {
if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
this._browserElementAPI._fireEvent('loadstart', this._getWindow(webProgress));
}
if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
this._browserElementAPI._fireEvent('loadend', this._getWindow(webProgress));
}
},
onStatusChange: function(webProgress, request, status, message) {},
onProgressChange: function(webProgress, request, curSelfProgress,
maxSelfProgress, curTotalProgress, maxTotalProgress) {},
onSecurityChange: function(webProgress, request, aState) {}
},
/**
* nsIObserver::Observe
*/
observe: function BA_observe(subject, topic, data) {
switch(topic) {
case 'app-startup':
this._init();
break;
case 'content-document-global-created':
this._observeContentGlobalCreated(subject);
break;
case 'docshell-marked-as-browser-frame':
this._observeDocshellMarkedAsBrowserFrame(subject);
break;
case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
if (data == BROWSER_FRAMES_ENABLED_PREF) {
this._init();
}
break;
}
},
};
var NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementAPI]);

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

@ -0,0 +1,3 @@
component {5d6fcab3-6c12-4db6-80fb-352df7a41602} BrowserElementAPI.js
contract @mozilla.org/browser-element-api;1 {5d6fcab3-6c12-4db6-80fb-352df7a41602}
category app-startup BrowserElementAPI service,@mozilla.org/browser-element-api;1

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

@ -54,6 +54,8 @@ DIRS = \
EXTRA_PP_COMPONENTS = \
ConsoleAPI.js \
ConsoleAPI.manifest \
BrowserElementAPI.js \
BrowserElementAPI.manifest \
$(NULL)
EXTRA_JS_MODULES = ConsoleAPIStorage.jsm \

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

@ -289,6 +289,8 @@
; JavaScript components
@BINPATH@/components/ConsoleAPI.manifest
@BINPATH@/components/ConsoleAPI.js
@BINPATH@/components/BrowserElementAPI.manifest
@BINPATH@/components/BrowserElementAPI.js
@BINPATH@/components/FeedProcessor.manifest
@BINPATH@/components/FeedProcessor.js
@BINPATH@/components/BrowserFeeds.manifest

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

@ -295,6 +295,8 @@
; JavaScript components
@BINPATH@/components/ConsoleAPI.manifest
@BINPATH@/components/ConsoleAPI.js
@BINPATH@/components/BrowserElementAPI.manifest
@BINPATH@/components/BrowserElementAPI.js
@BINPATH@/components/FeedProcessor.manifest
@BINPATH@/components/FeedProcessor.js
@BINPATH@/components/BrowserFeeds.manifest