Bug 962626 - Browser API: mozmetachanged triggered when a meta element is added to the dom., r=ehsan
authorAndrea Marchesini <amarchesini@mozilla.com>
Thu, 30 Jan 2014 09:31:34 -0800
changeset 182107 d696c67e248946e0359339a1c7e3f2846c250026
parent 182106 46df3fd9b0dccc895a3d400ec2e7d26ba2abc935
child 182108 1d7c2025bb562321c05f7f952e31f385828cb382
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs962626
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 962626 - Browser API: mozmetachanged triggered when a meta element is added to the dom., r=ehsan
dom/browser-element/BrowserElementChildPreload.js
dom/browser-element/BrowserElementParent.jsm
dom/browser-element/mochitest/Makefile.in
dom/browser-element/mochitest/browserElement_Metachange.js
dom/browser-element/mochitest/file_browserElement_Metachange.sjs
dom/browser-element/mochitest/test_browserElement_inproc_Metachange.html
dom/browser-element/mochitest/test_browserElement_oop_Metachange.html
dom/webidl/Document.webidl
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -181,16 +181,21 @@ BrowserElementChild.prototype = {
                      /* useCapture = */ true,
                      /* wantsUntrusted = */ false);
 
     addEventListener('DOMLinkAdded',
                      this._linkAddedHandler.bind(this),
                      /* useCapture = */ true,
                      /* wantsUntrusted = */ false);
 
+    addEventListener('DOMMetaAdded',
+                     this._metaAddedHandler.bind(this),
+                     /* useCapture = */ true,
+                     /* wantsUntrusted = */ false);
+
     // This listens to unload events from our message manager, but /not/ from
     // the |content| window.  That's because the window's unload event doesn't
     // bubble, and we're not using a capturing listener.  If we'd used
     // useCapture == true, we /would/ hear unload events from the window, which
     // is not what we want!
     addEventListener('unload',
                      this._unloadHandler.bind(this),
                      /* useCapture = */ false,
@@ -496,16 +501,64 @@ BrowserElementChild.prototype = {
     e.target.rel.split(' ').forEach(function(x) {
       let token = x.toLowerCase();
       if (handlers[token]) {
         handlers[token](e);
       }
     }, this);
   },
 
+  _metaAddedHandler: function(e) {
+    let win = e.target.ownerDocument.defaultView;
+    // Ignore metas which don't come from the top-level
+    // <iframe mozbrowser> window.
+    if (win != content) {
+      debug('Not top level!');
+      return;
+    }
+
+    if (!e.target.name) {
+      return;
+    }
+
+    debug('Got metaAdded: (' + e.target.name + ') ' + e.target.content);
+    if (e.target.name == 'application-name') {
+      let meta = { name: e.target.name,
+                   content: e.target.content };
+
+      let lang;
+      let elm;
+
+      for (elm = e.target;
+           !lang && elm && elm.nodeType == e.target.ELEMENT_NODE;
+           elm = elm.parentNode) {
+        if (elm.hasAttribute('lang')) {
+          lang = elm.getAttribute('lang');
+          continue;
+        }
+
+        if (elm.hasAttributeNS('http://www.w3.org/XML/1998/namespace', 'lang')) {
+          lang = elm.getAttributeNS('http://www.w3.org/XML/1998/namespace', 'lang');
+          continue;
+        }
+      }
+
+      // No lang has been detected.
+      if (!lang && elm.nodeType == e.target.DOCUMENT_NODE) {
+        lang = elm.contentLanguage;
+      }
+
+      if (lang) {
+        meta.lang = lang;
+      }
+
+      sendAsyncMsg('metachange', meta);
+    }
+  },
+
   _addMozAfterPaintHandler: function(callback) {
     function onMozAfterPaint() {
       let uri = docShell.QueryInterface(Ci.nsIWebNavigation).currentURI;
       if (uri.spec != "about:blank") {
         debug("Got afterpaint event: " + uri.spec);
         removeEventListener('MozAfterPaint', onMozAfterPaint,
                             /* useCapture = */ true);
         callback();
--- a/dom/browser-element/BrowserElementParent.jsm
+++ b/dom/browser-element/BrowserElementParent.jsm
@@ -253,16 +253,17 @@ BrowserElementParent.prototype = {
     let mmCalls = {
       "hello": this._recvHello,
       "contextmenu": this._fireCtxMenuEvent,
       "locationchange": this._fireEventFromMsg,
       "loadstart": this._fireEventFromMsg,
       "loadend": this._fireEventFromMsg,
       "titlechange": this._fireEventFromMsg,
       "iconchange": this._fireEventFromMsg,
+      "metachange": this._fireEventFromMsg,
       "close": this._fireEventFromMsg,
       "resize": this._fireEventFromMsg,
       "activitydone": this._fireEventFromMsg,
       "opensearch": this._fireEventFromMsg,
       "securitychange": this._fireEventFromMsg,
       "error": this._fireEventFromMsg,
       "scroll": this._fireEventFromMsg,
       "firstpaint": this._fireEventFromMsg,
--- a/dom/browser-element/mochitest/Makefile.in
+++ b/dom/browser-element/mochitest/Makefile.in
@@ -29,18 +29,21 @@ MOCHITEST_FILES = \
 		test_browserElement_inproc_TopBarrier.html \
 		browserElement_AppWindowNamespace.js \
 		test_browserElement_inproc_AppWindowNamespace.html \
 		file_browserElement_AppWindowNamespace.html \
 		browserElement_BrowserWindowNamespace.js \
 		test_browserElement_inproc_BrowserWindowNamespace.html \
 		file_browserElement_BrowserWindowNamespace.html \
 		browserElement_Iconchange.js \
+		browserElement_Metachange.js \
+		file_browserElement_Metachange.sjs \
 		browserElement_Opensearch.js \
 		test_browserElement_inproc_Iconchange.html \
+		test_browserElement_inproc_Metachange.html \
 		test_browserElement_inproc_Opensearch.html \
 		browserElement_GetScreenshot.js \
 		test_browserElement_inproc_GetScreenshot.html \
 		browserElement_BadScreenshot.js \
 		test_browserElement_inproc_BadScreenshot.html \
 		browserElement_SetVisible.js \
 		test_browserElement_inproc_SetVisible.html \
 		browserElement_SetVisibleFrames.js \
@@ -201,16 +204,17 @@ MOCHITEST_FILES += \
 		test_browserElement_oop_LoadEvents.html \
 		test_browserElement_oop_DataURI.html \
 		test_browserElement_oop_ErrorSecurity.html \
 		test_browserElement_oop_Titlechange.html \
 		test_browserElement_oop_AppWindowNamespace.html \
 		test_browserElement_oop_BrowserWindowNamespace.html \
 		test_browserElement_oop_TopBarrier.html \
 		test_browserElement_oop_Iconchange.html \
+		test_browserElement_oop_Metachange.html \
 		test_browserElement_oop_Opensearch.html \
 		test_browserElement_oop_GetScreenshot.html \
 		test_browserElement_oop_BadScreenshot.html \
 		test_browserElement_oop_XFrameOptions.html \
 		test_browserElement_oop_XFrameOptionsDeny.html \
 		test_browserElement_oop_XFrameOptionsSameOrigin.html \
 		test_browserElement_oop_XFrameOptionsAllowFrom.html \
 		test_browserElement_oop_SendEvent.html \
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/browserElement_Metachange.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the public domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the onmozbrowsermetachange event works.
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+browserElementTestHelpers.setEnabledPref(true);
+browserElementTestHelpers.addPermission();
+
+function createHtml(meta) {
+  return 'data:text/html,<html xmlns:xml="http://www.w3.org/XML/1998/namespace"><head>' + meta + '<body></body></html>';
+}
+
+function createHtmlWithLang(meta, lang) {
+  return 'data:text/html,<html xmlns:xml="http://www.w3.org/XML/1998/namespace" lang="' + lang + '"><head>' + meta + '<body></body></html>';
+}
+
+function createMeta(name, content) {
+  return '<meta name="' + name + '" content="' + content + '">';
+}
+
+function createMetaWithLang(name, content, lang) {
+  return '<meta name="' + name + '" content="' + content + '" lang="' + lang + '">';
+}
+
+function runTest() {
+  var iframe1 = document.createElement('iframe');
+  SpecialPowers.wrap(iframe1).mozbrowser = true;
+  document.body.appendChild(iframe1);
+
+  // iframe2 is a red herring; we modify its meta elements but don't listen for
+  // metachanges; we want to make sure that its metachange events aren't
+  // picked up by the listener on iframe1.
+  var iframe2 = document.createElement('iframe');
+  SpecialPowers.wrap(iframe2).mozbrowser = true;
+  document.body.appendChild(iframe2);
+
+  // iframe3 is another red herring.  It's not a mozbrowser, so we shouldn't
+  // get any metachange events on it.
+  var iframe3 = document.createElement('iframe');
+  document.body.appendChild(iframe3);
+
+  var numMetaChanges = 0;
+
+  iframe1.addEventListener('mozbrowsermetachange', function(e) {
+
+    numMetaChanges++;
+
+    if (numMetaChanges == 1) {
+      is(e.detail.name, 'application-name');
+      is(e.detail.content, 'foobar');
+
+      // We should recieve metachange events when the user creates new metas
+      SpecialPowers.getBrowserFrameMessageManager(iframe1)
+                   .loadFrameScript("data:,content.document.title='New title';",
+                                    /* allowDelayedLoad = */ false);
+
+      SpecialPowers.getBrowserFrameMessageManager(iframe1)
+                   .loadFrameScript("data:,content.document.head.insertAdjacentHTML('beforeend', '<meta name=application-name content=new_foobar>')",
+                                    /* allowDelayedLoad = */ false);
+
+      SpecialPowers.getBrowserFrameMessageManager(iframe2)
+                   .loadFrameScript("data:,content.document.head.insertAdjacentHTML('beforeend', '<meta name=application-name content=new_foobar>')",
+                                    /* allowDelayedLoad = */ false);
+    }
+    else if (numMetaChanges == 2) {
+      is(e.detail.name, 'application-name', 'name matches');
+      is(e.detail.content, 'new_foobar', 'content matches');
+      ok(!("lang" in e.detail), 'lang not present');
+
+      // Full new pages should trigger metachange events
+      iframe1.src = createHtml(createMeta('application-name', '3rd_foobar'));
+    }
+    else if (numMetaChanges == 3) {
+      is(e.detail.name, 'application-name', 'name matches');
+      is(e.detail.content, '3rd_foobar', 'content matches');
+      ok(!("lang" in e.detail), 'lang not present');
+
+      // Test setting a page with multiple meta elements
+      iframe1.src = createHtml(createMeta('application-name', 'foobar_1') + createMeta('application-name', 'foobar_2'));
+    }
+    else if (numMetaChanges == 4) {
+      is(e.detail.name, 'application-name', 'name matches');
+      is(e.detail.content, 'foobar_1', 'content matches');
+      ok(!("lang" in e.detail), 'lang not present');
+      // 2 events will be triggered by previous test, wait for next
+    }
+    else if (numMetaChanges == 5) {
+      is(e.detail.name, 'application-name', 'name matches');
+      is(e.detail.content, 'foobar_2', 'content matches');
+      ok(!("lang" in e.detail), 'lang not present');
+
+      // Test the language
+      iframe1.src = createHtml(createMetaWithLang('application-name', 'foobar_lang_1', 'en'));
+    }
+    else if (numMetaChanges == 6) {
+      is(e.detail.name, 'application-name', 'name matches');
+      is(e.detail.content, 'foobar_lang_1', 'content matches');
+      is(e.detail.lang, 'en', 'language matches');
+
+      // Test the language in the ancestor element
+      iframe1.src = createHtmlWithLang(createMeta('application-name', 'foobar_lang_2'), 'es');
+    }
+    else if (numMetaChanges == 7) {
+      is(e.detail.name, 'application-name', 'name matches');
+      is(e.detail.content, 'foobar_lang_2', 'content matches');
+      is(e.detail.lang, 'es', 'language matches');
+
+      // Test the language in the ancestor element
+      iframe1.src = createHtmlWithLang(createMetaWithLang('application-name', 'foobar_lang_3', 'it'), 'fi');
+    }
+    else if (numMetaChanges == 8) {
+      is(e.detail.name, 'application-name', 'name matches');
+      is(e.detail.content, 'foobar_lang_3', 'content matches');
+      is(e.detail.lang, 'it', 'language matches');
+
+      // Test the content-language
+      iframe1.src = "http://test/tests/dom/browser-element/mochitest/file_browserElement_Metachange.sjs?ru";
+    }
+    else if (numMetaChanges == 9) {
+      is(e.detail.name, 'application-name', 'name matches');
+      is(e.detail.content, 'sjs', 'content matches');
+      is(e.detail.lang, 'ru', 'language matches');
+
+      // Test the content-language
+      iframe1.src = "http://test/tests/dom/browser-element/mochitest/file_browserElement_Metachange.sjs?ru|dk";
+    }
+    else if (numMetaChanges == 10) {
+      is(e.detail.name, 'application-name', 'name matches');
+      is(e.detail.content, 'sjs', 'content matches');
+      is(e.detail.lang, 'dk', 'language matches');
+
+      // Test the language
+      SimpleTest.finish();
+    } else {
+      ok(false, 'Too many metachange events.');
+    }
+  });
+
+  iframe3.addEventListener('mozbrowsermetachange', function(e) {
+    ok(false, 'Should not get a metachange event for iframe3.');
+  });
+
+
+  iframe1.src = createHtml(createMeta('application-name', 'foobar'));
+  // We should not recieve meta change events for either of the below iframes
+  iframe2.src = createHtml(createMeta('application-name', 'foobar'));
+  iframe3.src = createHtml(createMeta('application-name', 'foobar'));
+
+}
+
+addEventListener('testready', runTest);
new file mode 100644
--- /dev/null
+++ b/dom/browser-element/mochitest/file_browserElement_Metachange.sjs
@@ -0,0 +1,7 @@
+function handleRequest(request, response)
+{
+  var p = request.queryString.split('|');
+  response.setHeader('Content-Language', p[0], false);
+  response.write('<html><head><meta name="application-name" content="sjs"' +
+                  (p.length > 1 ? (' lang="' + p[1] + '"') : '') + '></head><body></body></html>');
+}
copy from dom/browser-element/mochitest/test_browserElement_inproc_Iconchange.html
copy to dom/browser-element/mochitest/test_browserElement_inproc_Metachange.html
--- a/dom/browser-element/mochitest/test_browserElement_inproc_Iconchange.html
+++ b/dom/browser-element/mochitest/test_browserElement_inproc_Metachange.html
@@ -1,19 +1,19 @@
 <!DOCTYPE HTML>
 <html>
 <!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=719461
+https://bugzilla.mozilla.org/show_bug.cgi?id=962626
 -->
 <head>
-  <title>Test for Bug 719461</title>
+  <title>Test for Bug 962626</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="browserElementTestHelpers.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=719461">Mozilla Bug 719461</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=962626">Mozilla Bug 962626</a>
 
-<script type="application/javascript;version=1.7" src="browserElement_Iconchange.js">
+<script type="application/javascript;version=1.7" src="browserElement_Metachange.js">
 </script>
 
 </body>
 </html>
copy from dom/browser-element/mochitest/test_browserElement_oop_Iconchange.html
copy to dom/browser-element/mochitest/test_browserElement_oop_Metachange.html
--- a/dom/browser-element/mochitest/test_browserElement_oop_Iconchange.html
+++ b/dom/browser-element/mochitest/test_browserElement_oop_Metachange.html
@@ -1,19 +1,19 @@
 <!DOCTYPE HTML>
 <html>
 <!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=719461
+https://bugzilla.mozilla.org/show_bug.cgi?id=962626
 -->
 <head>
-  <title>Test for Bug 719461</title>
+  <title>Test for Bug 962626</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="browserElementTestHelpers.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=719461">Mozilla Bug 719461</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=962626">Mozilla Bug 962626</a>
 
-<script type="application/javascript;version=1.7" src="browserElement_Iconchange.js">
+<script type="application/javascript;version=1.7" src="browserElement_Metachange.js">
 </script>
 
 </body>
 </html>
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -336,16 +336,18 @@ partial interface Document {
   attribute boolean styleSheetChangeEventsEnabled;
 
   [ChromeOnly, Throws]
   void obsoleteSheet(URI sheetURI);
   [ChromeOnly, Throws]
   void obsoleteSheet(DOMString sheetURI);
 
   [ChromeOnly] readonly attribute nsIDocShell? docShell;
+
+  [ChromeOnly] readonly attribute DOMString contentLanguage;
 };
 
 // Extension to give chrome JS the ability to determine when a document was
 // created to satisfy an iframe with srcdoc attribute.
 partial interface Document {
   [ChromeOnly] readonly attribute boolean isSrcdocDocument;
 };