diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index b5282dc9da0..10951b5e2f3 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -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, - // 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); }, ///////////////////////////////////////////////////////////////////////////// diff --git a/browser/base/content/test/Makefile.in b/browser/base/content/test/Makefile.in index 211273cd28f..0d0e2a371fb 100644 --- a/browser/base/content/test/Makefile.in +++ b/browser/base/content/test/Makefile.in @@ -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 diff --git a/browser/base/content/test/offlineChild.cacheManifest b/browser/base/content/test/offlineChild.cacheManifest new file mode 100644 index 00000000000..091fe71940e --- /dev/null +++ b/browser/base/content/test/offlineChild.cacheManifest @@ -0,0 +1,2 @@ +CACHE MANIFEST +offlineChild.html diff --git a/browser/base/content/test/offlineChild.cacheManifest^headers^ b/browser/base/content/test/offlineChild.cacheManifest^headers^ new file mode 100644 index 00000000000..257f2eb60f1 --- /dev/null +++ b/browser/base/content/test/offlineChild.cacheManifest^headers^ @@ -0,0 +1 @@ +Content-Type: text/cache-manifest diff --git a/browser/base/content/test/offlineChild.html b/browser/base/content/test/offlineChild.html new file mode 100644 index 00000000000..43f225b3b00 --- /dev/null +++ b/browser/base/content/test/offlineChild.html @@ -0,0 +1,20 @@ + + + + + + + +

Child

+ + diff --git a/browser/base/content/test/offlineChild2.cacheManifest b/browser/base/content/test/offlineChild2.cacheManifest new file mode 100644 index 00000000000..19efe54fe3b --- /dev/null +++ b/browser/base/content/test/offlineChild2.cacheManifest @@ -0,0 +1,2 @@ +CACHE MANIFEST +offlineChild2.html diff --git a/browser/base/content/test/offlineChild2.cacheManifest^headers^ b/browser/base/content/test/offlineChild2.cacheManifest^headers^ new file mode 100644 index 00000000000..257f2eb60f1 --- /dev/null +++ b/browser/base/content/test/offlineChild2.cacheManifest^headers^ @@ -0,0 +1 @@ +Content-Type: text/cache-manifest diff --git a/browser/base/content/test/offlineChild2.html b/browser/base/content/test/offlineChild2.html new file mode 100644 index 00000000000..ac762e75962 --- /dev/null +++ b/browser/base/content/test/offlineChild2.html @@ -0,0 +1,20 @@ + + + + + + + +

Child

+ + diff --git a/browser/base/content/test/test_offlineNotification.html b/browser/base/content/test/test_offlineNotification.html new file mode 100644 index 00000000000..766ab0b88ec --- /dev/null +++ b/browser/base/content/test/test_offlineNotification.html @@ -0,0 +1,68 @@ + + + + + Test offline app notification + + + + + +

+ + + + + + +

+
+
+
+ + + diff --git a/content/base/public/nsContentUtils.h b/content/base/public/nsContentUtils.h index 2dd50c83a38..5f5c212b149 100644 --- a/content/base/public/nsContentUtils.h +++ b/content/base/public/nsContentUtils.h @@ -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 diff --git a/content/base/src/nsContentUtils.cpp b/content/base/src/nsContentUtils.cpp index d599ed143c1..d932efbfa30 100644 --- a/content/base/src/nsContentUtils.cpp +++ b/content/base/src/nsContentUtils.cpp @@ -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 docEvent = do_QueryInterface(aDoc); + nsCOMPtr originalTarget = + do_QueryInterface(aTarget); + NS_ENSURE_TRUE(docEvent && originalTarget, NS_ERROR_INVALID_ARG); + + nsCOMPtr event; + nsresult rv = + docEvent->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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) diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 57b5619533d..36d4d7abe6a 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -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(this), + NS_LITERAL_STRING("MozApplicationManifest"), + PR_TRUE, PR_TRUE); + } + UnblockOnload(PR_TRUE); }