Bug 462856 - offline app notification code doesn't handle subframes. r=mconnor, r=bz

This commit is contained in:
Dave Camp 2009-02-22 11:09:10 -08:00
parent 19961382d3
commit 19435fea38
12 changed files with 250 additions and 29 deletions

View File

@ -1155,6 +1155,10 @@ function prepareForStartup() {
// setup our common DOMLinkAdded listener
gBrowser.addEventListener("DOMLinkAdded", DOMLinkHandler, false);
// setup our MozApplicationManifest listener
gBrowser.addEventListener("MozApplicationManifest",
OfflineApps, false);
// setup simple gestures support
gGestureSupport.init(true);
}
@ -3875,10 +3879,6 @@ var XULBrowserWindow = {
this.endDocumentLoad(aRequest, aStatus);
if (!gBrowser.mTabbedMode && !gBrowser.mCurrentBrowser.mIconURL)
gBrowser.useDefaultIcon(gBrowser.mCurrentTab);
if (Components.isSuccessCode(aStatus) &&
content.document.documentElement.getAttribute("manifest"))
OfflineApps.offlineAppRequested(content);
}
}
@ -5334,6 +5334,12 @@ var OfflineApps = {
obs.removeObserver(this, "offline-cache-update-completed");
},
handleEvent: function(event) {
if (event.type == "MozApplicationManifest") {
this.offlineAppRequested(event.originalTarget.defaultView);
}
},
/////////////////////////////////////////////////////////////////////////////
// OfflineApps Implementation Methods
@ -5360,6 +5366,7 @@ var OfflineApps = {
},
_getManifestURI: function(aWindow) {
if (!aWindow.document.documentElement) return null;
var attr = aWindow.document.documentElement.getAttribute("manifest");
if (!attr) return null;
@ -5481,7 +5488,7 @@ var OfflineApps = {
var browser = this._getBrowserForContentWindow(browserWindow,
aContentWindow);
var currentURI = browser.webNavigation.currentURI;
var currentURI = aContentWindow.document.documentURIObject;
var pm = Cc["@mozilla.org/permissionmanager;1"].
getService(Ci.nsIPermissionManager);
@ -5499,19 +5506,32 @@ var OfflineApps = {
// this pref isn't set by default, ignore failures
}
var host = currentURI.asciiHost;
var notificationBox = gBrowser.getNotificationBox(browser);
var notification = notificationBox.getNotificationWithValue("offline-app-requested");
if (!notification) {
var notificationID = "offline-app-requested-" + host;
var notification = notificationBox.getNotificationWithValue(notificationID);
if (notification) {
notification.documents.push(aContentWindow.document);
} else {
var bundle_browser = document.getElementById("bundle_browser");
var buttons = [{
label: bundle_browser.getString("offlineApps.allow"),
accessKey: bundle_browser.getString("offlineApps.allowAccessKey"),
callback: function() { OfflineApps.allowSite(); }
callback: function() {
for (var i = 0; i < notification.documents.length; i++) {
OfflineApps.allowSite(notification.documents[i]);
}
}
},{
label: bundle_browser.getString("offlineApps.never"),
accessKey: bundle_browser.getString("offlineApps.neverAccessKey"),
callback: function() { OfflineApps.disallowSite(); }
callback: function() {
for (var i = 0; i < notification.documents.length; i++) {
OfflineApps.disallowSite(notification.documents[i]);
}
}
},{
label: bundle_browser.getString("offlineApps.notNow"),
accessKey: bundle_browser.getString("offlineApps.notNowAccessKey"),
@ -5520,51 +5540,55 @@ var OfflineApps = {
const priority = notificationBox.PRIORITY_INFO_LOW;
var message = bundle_browser.getFormattedString("offlineApps.available",
[ currentURI.host ]);
notificationBox.appendNotification(message, "offline-app-requested",
"chrome://browser/skin/Info.png",
priority, buttons);
[ host ]);
notification =
notificationBox.appendNotification(message, notificationID,
"chrome://browser/skin/Info.png",
priority, buttons);
notification.documents = [ aContentWindow.document ];
}
},
allowSite: function() {
var currentURI = gBrowser.selectedBrowser.webNavigation.currentURI;
allowSite: function(aDocument) {
var pm = Cc["@mozilla.org/permissionmanager;1"].
getService(Ci.nsIPermissionManager);
pm.add(currentURI, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
pm.add(aDocument.documentURIObject, "offline-app",
Ci.nsIPermissionManager.ALLOW_ACTION);
// When a site is enabled while loading, <link rel="offline-resource">
// resources will start fetching immediately. This one time we need to
// do it ourselves.
this._startFetching();
// When a site is enabled while loading, manifest resources will
// start fetching immediately. This one time we need to do it
// ourselves.
this._startFetching(aDocument);
},
disallowSite: function() {
var currentURI = gBrowser.selectedBrowser.webNavigation.currentURI;
disallowSite: function(aDocument) {
var pm = Cc["@mozilla.org/permissionmanager;1"].
getService(Ci.nsIPermissionManager);
pm.add(currentURI, "offline-app", Ci.nsIPermissionManager.DENY_ACTION);
pm.add(aDocument.documentURIObject, "offline-app",
Ci.nsIPermissionManager.DENY_ACTION);
},
manage: function() {
openAdvancedPreferences("networkTab");
},
_startFetching: function() {
var manifest = content.document.documentElement.getAttribute("manifest");
_startFetching: function(aDocument) {
if (!aDocument.documentElement)
return;
var manifest = aDocument.documentElement.getAttribute("manifest");
if (!manifest)
return;
var ios = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
var contentURI = ios.newURI(content.location.href, null, null);
var manifestURI = ios.newURI(manifest, content.document.characterSet,
contentURI);
var manifestURI = ios.newURI(manifest, aDocument.characterSet,
aDocument.documentURIObject);
var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].
getService(Ci.nsIOfflineCacheUpdateService);
updateService.scheduleUpdate(manifestURI, contentURI);
updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject);
},
/////////////////////////////////////////////////////////////////////////////

View File

@ -50,6 +50,13 @@ _TEST_FILES = test_feed_discovery.html \
test_contextmenu.html \
subtst_contextmenu.html \
ctxmenu-image.png \
test_offlineNotification.html \
offlineChild.html \
offlineChild.cacheManifest \
offlineChild.cacheManifest^headers^ \
offlineChild2.html \
offlineChild2.cacheManifest \
offlineChild2.cacheManifest^headers^ \
$(NULL)
# browser_bug423833.js disabled temporarily since it's unreliable: bug 428712

View File

@ -0,0 +1,2 @@
CACHE MANIFEST
offlineChild.html

View File

@ -0,0 +1 @@
Content-Type: text/cache-manifest

View File

@ -0,0 +1,20 @@
<html manifest="offlineChild.cacheManifest">
<head>
<title></title>
<script type="text/javascript">
function finish(success) {
window.parent.postMessage(success ? "success" : "failure", "*");
}
applicationCache.oncached = function() { finish(true); }
applicationCache.onnoupdate = function() { finish(true); }
applicationCache.onerror = function() { finish(false); }
</script>
</head>
<body>
<h1>Child</h1>
</body>
</html>

View File

@ -0,0 +1,2 @@
CACHE MANIFEST
offlineChild2.html

View File

@ -0,0 +1 @@
Content-Type: text/cache-manifest

View File

@ -0,0 +1,20 @@
<html manifest="offlineChild2.cacheManifest">
<head>
<title></title>
<script type="text/javascript">
function finish(success) {
window.parent.postMessage(success ? "success" : "failure", "*");
}
applicationCache.oncached = function() { finish(true); }
applicationCache.onnoupdate = function() { finish(true); }
applicationCache.onerror = function() { finish(false); }
</script>
</head>
<body>
<h1>Child</h1>
</body>
</html>

View File

@ -0,0 +1,68 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=462856
-->
<head>
<title>Test offline app notification</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body onload="loaded()">
<p id="display">
<!-- Load the test frame twice from the same domain,
to make sure we get notifications for both -->
<iframe name="testFrame" src="offlineChild.html"></iframe>
<iframe name="testFrame2" src="offlineChild2.html"></iframe>
<!-- Load from another domain to make sure we get a second allow/deny
notification -->
<iframe name="testFrame3" src="http://example.com/tests/browser/base/content/test/offlineChild.html"></iframe>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
var numFinished = 0;
window.addEventListener("message", function(event) {
is(event.data, "success", "Child was successfully cached.");
if (++numFinished == 3) {
// Clean up after ourself
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var pm = Components.classes["@mozilla.org/permissionmanager;1"].
getService(Components.interfaces.nsIPermissionManager);
pm.remove(frames.testFrame.document.documentURIObject.host, "offline-app");
pm.remove(frames.testFrame3.document.documentURIObject.host, "offline-app");
SimpleTest.finish();
}
}, false);
function loaded() {
// Click the notification bar's "Allow" button. This should kick
// off updates, which will eventually lead to getting messages from
// the children.
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].
getService(Components.interfaces.nsIWindowMediator);
var win = wm.getMostRecentWindow("navigator:browser");
var notificationBox = win.gBrowser.getNotificationBox();
var notification = notificationBox.getNotificationWithValue("offline-app-requested-localhost");
notification.childNodes[0].click();
notification = notificationBox.getNotificationWithValue("offline-app-requested-example.com");
notification.childNodes[0].click();
}
</script>
</pre>
</body>
</html>

View File

@ -883,6 +883,28 @@ public:
PRBool aCancelable,
PRBool *aDefaultAction = nsnull);
/**
* This method creates and dispatches a trusted event to the chrome
* event handler.
* Works only with events which can be created by calling
* nsIDOMDocumentEvent::CreateEvent() with parameter "Events".
* @param aDocument The document which will be used to create the event,
* and whose window's chrome handler will be used to
* dispatch the event.
* @param aTarget The target of the event, used for event->SetTarget()
* @param aEventName The name of the event.
* @param aCanBubble Whether the event can bubble.
* @param aCancelable Is the event cancelable.
* @param aDefaultAction Set to true if default action should be taken,
* see nsIDOMEventTarget::DispatchEvent.
*/
static nsresult DispatchChromeEvent(nsIDocument* aDoc,
nsISupports* aTarget,
const nsAString& aEventName,
PRBool aCanBubble,
PRBool aCancelable,
PRBool *aDefaultAction = nsnull);
/**
* Determines if an event attribute name (such as onclick) is valid for
* a given element type. Types are from the EventNameType enumeration

View File

@ -3124,6 +3124,51 @@ nsContentUtils::DispatchTrustedEvent(nsIDocument* aDoc, nsISupports* aTarget,
return target->DispatchEvent(event, aDefaultAction ? aDefaultAction : &dummy);
}
nsresult
nsContentUtils::DispatchChromeEvent(nsIDocument *aDoc,
nsISupports *aTarget,
const nsAString& aEventName,
PRBool aCanBubble, PRBool aCancelable,
PRBool *aDefaultAction)
{
NS_ENSURE_ARG_POINTER(aDoc);
NS_ENSURE_ARG_POINTER(aDoc->GetWindow());
NS_ENSURE_ARG_POINTER(aDoc->GetWindow()->GetChromeEventHandler());
nsCOMPtr<nsIDOMDocumentEvent> docEvent = do_QueryInterface(aDoc);
nsCOMPtr<nsIDOMEventTarget> originalTarget =
do_QueryInterface(aTarget);
NS_ENSURE_TRUE(docEvent && originalTarget, NS_ERROR_INVALID_ARG);
nsCOMPtr<nsIDOMEvent> event;
nsresult rv =
docEvent->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
NS_ENSURE_TRUE(privateEvent, NS_ERROR_FAILURE);
rv = event->InitEvent(aEventName, aCanBubble, aCancelable);
NS_ENSURE_SUCCESS(rv, rv);
rv = privateEvent->SetTarget(originalTarget);
NS_ENSURE_SUCCESS(rv, rv);
rv = privateEvent->SetTrusted(PR_TRUE);
NS_ENSURE_SUCCESS(rv, rv);
nsEventStatus status = nsEventStatus_eIgnore;
rv = aDoc->GetWindow()->GetChromeEventHandler()->DispatchDOMEvent(nsnull,
event,
nsnull,
&status);
if (aDefaultAction) {
*aDefaultAction = (status != nsEventStatus_eConsumeNoDefault);
}
return rv;
}
/* static */
nsIContent*
nsContentUtils::MatchElementId(nsIContent *aContent, nsIAtom* aId)

View File

@ -3980,6 +3980,15 @@ nsDocument::DispatchContentLoadedEvents()
} while (parent);
}
// If the document has a manifest attribute, fire a MozApplicationManifest
// event.
nsIContent* root = GetRootContent();
if (root && root->HasAttr(kNameSpaceID_None, nsGkAtoms::manifest)) {
nsContentUtils::DispatchChromeEvent(this, static_cast<nsIDocument*>(this),
NS_LITERAL_STRING("MozApplicationManifest"),
PR_TRUE, PR_TRUE);
}
UnblockOnload(PR_TRUE);
}