Revision: 5470
Author:   [email protected]
Date:     Tue Jul  2 14:37:42 2013
Log:      Whitelist <video> and <audio> content.
https://codereview.appspot.com/10882043

* Add AUDIO::SRC and VIDEO::SRC to whitelist.
* Tame HTMLMediaElement, HTMLAudioElement, and HTMLVideoElement.
* Add Audio ctor.

Supporting changes:
* The PT.filter(false, identity, true, identity) pattern shall now be
  known as NP_writePolicyOnly, with comments. More refactoring to come.
* Slightly generalize handling of Image and Option ctors.

[email protected]

http://code.google.com/p/google-caja/source/detail?r=5470

Modified:
 /trunk/src/com/google/caja/lang/html/html5-attributes-whitelist.json
 /trunk/src/com/google/caja/plugin/domado.js
 /trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html
 /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js

=======================================
--- /trunk/src/com/google/caja/lang/html/html5-attributes-whitelist.json Wed Jun 19 13:54:01 2013 +++ /trunk/src/com/google/caja/lang/html/html5-attributes-whitelist.json Tue Jul 2 14:37:42 2013
@@ -76,6 +76,8 @@
       "OL::REVERSED",
       "*::SPELLCHECK",
       "LINK::SIZES",
+      "AUDIO::SRC",
+      "VIDEO::SRC",
       "TRACK::SRCLANG",
       "INPUT::STEP",
       "*::TRANSLATE",
@@ -253,10 +255,6 @@
"TODO(kpreid): Stop migrating style elements so that this works" },
       { "key": "IFRAME::SEAMLESS",
         "comment": "TODO(kpreid): further review" },
-      { "key": "AUDIO::SRC",
-        "comment": "TODO(kpreid): further review" },
-      { "key": "VIDEO::SRC",
-        "comment": "TODO(kpreid): further review" },
       { "key": "IFRAME::SRCDOC",
         "comment": "TODO(kpreid): Implement HTML atype sanitization" }
       ]
=======================================
--- /trunk/src/com/google/caja/plugin/domado.js Thu Jun 20 09:42:14 2013
+++ /trunk/src/com/google/caja/plugin/domado.js Tue Jul  2 14:37:42 2013
@@ -1851,6 +1851,11 @@
       var tamingClassTable = new TamingClassTable();
       var inertCtor = tamingClassTable.inertCtor.bind(tamingClassTable);

+      // Table of functions which are what WebIDL calls [NamedConstructor]
+ // (Caveat: In actual browsers, e.g. new Image().constructor === Image !==
+      // HTMLImageElement. We don't implement that.)
+      var namedConstructors = {};
+
       // The private properties used in TameNodeConf are:
       //    feral (feral node)
       //    policy (access policy)
@@ -2628,6 +2633,15 @@
       var NP_noArgEditVoidMethod = NP_NoArgEditMethod(noop);
var NP_noArgEditMethodReturningNode = NP_NoArgEditMethod(defaultTameNode);

+      /**
+ * Property spec for properties which reflect attributes whose values are + * not rewritten (so we can use the underlying property on getting) but
+       * should, on setting, be restricted by the attribute whitelist.
+       */
+ // This alias exists so as to document *why* we're doing this particular
+      // configuration.
+      var NP_writePolicyOnly = PT.filter(false, identity, true, identity);
+
       var nodeClassNoImplWarnings = {};
       var elementTamerCache = {};
       function makeTameNodeByType(node) {
@@ -4404,10 +4418,21 @@
             // TODO(felix8a): add suffix if href is self
             identity),
           // TODO(felix8a): fragment rewriting?
-          href: PT.filter(false, identity, true, identity)
+          href: NP_writePolicyOnly
         }; }
       });

+      defineElement({
+        superclass: 'HTMLMediaElement',
+        domClass: 'HTMLAudioElement'
+      });
+      namedConstructors.Audio = innocuous(function AudioCtor(src) {
+        var element = tameDocument.createElement('audio');
+        element.preload = 'auto';
+        if (src !== undefined) { element.src = src; }
+        return element;
+      });
+
       defineTrivialElement('HTMLBRElement');

       defineElement({
@@ -5317,6 +5342,13 @@
               PT.filterProp(Boolean, Boolean))
         }; }
       });
+ // Per https://developer.mozilla.org/en-US/docs/DOM/Image as of 2012-09-24 + namedConstructors.Image = innocuous(function ImageCtor(width, height) {
+        var element = tameDocument.createElement('img');
+        if (width !== undefined) { element.width = width; }
+        if (height !== undefined) { element.height = height; }
+        return element;
+      });

// Common supertype just to save some code -- does not correspond to real
       // HTML, but should be harmless. Ideally we wouldn't export this.
@@ -5394,6 +5426,69 @@
         }; }
       });

+      defineElement({
+        domClass: 'HTMLMediaElement',
+        properties: function() { return {
+          // TODO(kpreid): audioTracks taming
+          autoplay: NP_writePolicyOnly,
+          // TODO(kpreid): buffered (TimeRanges) taming
+          // TODO(kpreid): controller (MediaController) taming
+          controls: NP_writePolicyOnly,
+          crossOrigin: NP_writePolicyOnly,
+          currentSrc: PT.ro,
+          currentTime: PT.ro,
+          defaultMuted: {
+            // TODO: express this generically
+            enumerable: true,
+            get: nodeAmp(function(privates) {
+              return privates.feral.defaultMuted;
+            }),
+            set: innocuous(function(v) {
+              if (v) {
+                this.setAttribute('muted', '');
+              } else {
+                this.removeAttribute('muted');
+              }
+            })
+          },
+          defaultPlaybackRate: PT.filterProp(identity, Number),
+          duration: PT.ro,
+          ended: PT.ro,
+          // TODO(kpreid): error (MediaError) taming
+          loop: NP_writePolicyOnly,
+ mediaGroup: PT.filterAttr(identity, identity), // rewritten like id
+          muted: PT.ro,  // TODO(kpreid): Pending policy about guest audio
+          networkState: PT.ro,
+          paused: PT.ro,
+          playbackRate: PT.filterProp(identity, Number),
+          // TODO(kpreid): played (TimeRanges) taming
+          preload: NP_writePolicyOnly,
+          readyState: PT.ro,
+          // TODO(kpreid): seekable (TimeRanges) taming
+          seeking: PT.ro,
+          src: NP_writePolicyOnly,
+          // TODO(kpreid): textTracks (TextTrackList) taming
+          // TODO(kpreid): videoTracks (VideoTrackList) taming
+          volume: PT.ro,  // TODO(kpreid): Pending policy about guest audio
+          canPlayType: Props.ampMethod(function(privates, type) {
+            return String(privates.feral.canPlayType(String(type)));
+          }),
+          fastSeek: Props.ampMethod(function(privates, time) {
+            // TODO(kpreid): Use generic taming like canvas does
+            privates.policy.requireEditable();
+            privates.feral.fastSeek(+time);
+          }),
+          load: NP_noArgEditVoidMethod,
+          pause: NP_noArgEditVoidMethod,
+          play: Props.ampMethod(function(privates, time) {
+            // TODO(kpreid): Better programmatic control approach
+            if (domicile.handlingUserAction) {
+              privates.feral.play();
+            }
+          })
+        }; }
+      });
+
       defineElement({
         domClass: 'HTMLOptionElement',
         properties: function() { return {
@@ -5410,7 +5505,20 @@
             function (x) { return x == null ? '' : '' + x; })
         }; }
       });
-
+      // Per https://developer.mozilla.org/en-US/docs/DOM/Option
+      // as of 2012-09-24
+      namedConstructors.Option = innocuous(function OptionCtor(
+          text, value, defaultSelected, selected) {
+        var element = tameDocument.createElement('option');
+        if (text !== undefined) { element.text = text; }
+        if (value !== undefined) { element.value = value; }
+        if (defaultSelected !== undefined) {
+          element.defaultSelected = defaultSelected;
+        }
+        if (selected !== undefined) { element.selected = selected; }
+        return element;
+      });
+
       defineTrivialElement('HTMLParagraphElement');
       defineTrivialElement('HTMLPreElement');

@@ -5457,7 +5565,7 @@
           // document.createElement'd scripts for src loading
         }),
         properties: function() { return {
-          src: PT.filter(false, identity, true, identity),
+          src: NP_writePolicyOnly,
           setAttribute: Props.ampMethod(function(privates, attrib, value) {
             var feral = privates.feral;
             privates.policy.requireEditable();
@@ -5480,7 +5588,7 @@
               document.createElement('style'));
           return {
             disabled: PT.filterProp(identity, Boolean),
-            media: PT.filter(false, identity, true, identity),
+            media: NP_writePolicyOnly,
             scoped: Props.cond('scoped' in styleForFeatureTests,
                 PT.filterProp(identity, Boolean)),
             // TODO(kpreid): property 'sheet'
@@ -5615,38 +5723,20 @@
         virtualized: null,
         domClass: 'HTMLUnknownElement'
       });
-
- // We are now done with all of the specialized element taming classes.

-      // Oddball constructors. There are only two of these and we implement
- // both. (Caveat: In actual browsers, new Image().constructor == Image
-      // != HTMLImageElement. We don't implement that.)
- // TODO(kpreid): There are more oddball constructors in HTML5, e.g. Audio, - // and a notation for it in HTML5's WebIDL. Generalize this to support
-      // that.
-
- // Per https://developer.mozilla.org/en-US/docs/DOM/Image as of 2012-09-24
-      function TameImageFun(width, height) {
-        var element = tameDocument.createElement('img');
-        if (width !== undefined) { element.width = width; }
-        if (height !== undefined) { element.height = height; }
-        return element;
-      }
-      cajaVM.def(TameImageFun);
-
-      // Per https://developer.mozilla.org/en-US/docs/DOM/Option
-      // as of 2012-09-24
-      function TameOptionFun(text, value, defaultSelected, selected) {
-        var element = tameDocument.createElement('option');
-        if (text !== undefined) { element.text = text; }
-        if (value !== undefined) { element.value = value; }
-        if (defaultSelected !== undefined) {
-          element.defaultSelected = defaultSelected;
-        }
-        if (selected !== undefined) { element.selected = selected; }
-        return element;
-      }
-      cajaVM.def(TameOptionFun);
+      defineElement({
+        superclass: 'HTMLMediaElement',
+        domClass: 'HTMLVideoElement',
+        properties: function() { return {
+          height: PT.rw,
+          width: PT.rw,
+          poster: PT.filterAttr(identity, identity),
+          videoHeight: PT.rw,
+          videoWidth: PT.rw
+        }; }
+      });
+
+ // We are now done with all of the specialized element taming classes.

       // Taming of Events:

@@ -6942,8 +7032,16 @@
         }
       }

-      tameWindow.Image = TameImageFun;
-      tameWindow.Option = TameOptionFun;
+      // Register [NamedConstructor]s
+ Object.freeze(namedConstructors); // catch initialization order errors + Object.getOwnPropertyNames(namedConstructors).forEach(function(name) {
+        Object.defineProperty(tameWindow, name, {
+          enumerable: true,
+          configurable: true,
+          writable: true,
+          value: namedConstructors[name]
+        });
+      });

       tameDocument = finishNode(tameDocument);

=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html Mon Jun 24 11:58:58 2013 +++ /trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html Tue Jul 2 14:37:42 2013
@@ -2259,6 +2259,10 @@
         typenames: [],
         sample: window.navigator
       },
+      {
+        typenames: [],
+        sample: new Audio()
+      },
       {
         typenames: [],
         sample: new Image()
@@ -2669,8 +2673,19 @@
                  function testOddballConstructors() {
// Note: This test also tests the properties of the img and option elements.

+    var audio1 = new Audio();
+    assertEquals('AUDIO', audio1.nodeName);
+    assertEquals('audio default src', '', audio1.src);
+    assertEquals('audio implicit preload', 'auto', audio1.preload);
+    var audio2 = new Audio('foo.wav');
+ // TODO(kpreid): This should be assertEquals but URI rewriting is visible
+    assertContains('audio supplied src', 'foo.wav', audio2.src);
+    document.getElementById('testOddballConstructors').appendChild(audio2);
+        // should not fail - i.e. is a valid element
+
     // new Image() is approximately document.createElement('img')
     var image1 = new Image();
+    assertEquals('IMG', image1.nodeName);
     assertEquals('image default nodeType', 1, image1.nodeType);
     assertEquals('image default tagName', 'IMG', image1.tagName);
     assertEquals('image default width', 0, image1.width);
@@ -2684,6 +2699,7 @@

     // new Option() is approximately document.createElement('option')
     var option1 = new Option();
+    assertEquals('OPTION', option1.nodeName);
     assertEquals('option default nodeType', 1, option1.nodeType);
     assertEquals('option default tagName', 'OPTION', option1.tagName);
     assertEquals('option default text', '', option1.text);
=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js Fri Jun 21 14:57:00 2013 +++ /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js Tue Jul 2 14:37:42 2013
@@ -22,8 +22,9 @@
  *     setTimeout, clearTimeout, setInterval, clearInterval,
  *     cajaVM, directAccess, inES5Mode, getUrlParam,
  *     assertTrue, assertEquals, pass, jsunitFail,
- * Event, HTMLInputElement, HTMLTableRowElement, HTMLTableSectionElement, - * HTMLTableElement, Image, Option, XMLHttpRequest, Window, Document, Node,
+ *     Event, HTMLInputElement, HTMLMediaElement, HTMLTableRowElement,
+ *     HTMLTableSectionElement, HTMLTableElement,
+ *     Audio, Image, Option, XMLHttpRequest, Window, Document, Node,
  *     Attr, Text, CSSStyleDeclaration, CanvasRenderingContext2D,
  *     CanvasGradient, ImageData, Location
  * @overrides window
@@ -669,7 +670,7 @@
         }
         // TODO(kpreid): factor out recognition
         if (typeof object === 'function' &&
-            /function Tame(?!XMLHttpRequest|OptionFun|ImageFun)/
+            /function Tame(?!XMLHttpRequest)/
                 .test(object.toString())) {
           noteProblem('Object is a taming ctor', context);
         }
@@ -940,6 +941,8 @@
         'backgroundColor', 'content', 'foo');
var genCSSColor = G.value(undefined, null, '', 'red', '#17F', 'octarine',
         '}{');
+    var genMediaType = G.value(undefined, '', '/', 'text', 'text/*',
+        'text/plain', 'image/*', 'image/gif', 'audio/*', 'audio/x-wav');
     var genJS = G.value(null, '', '{', 'return true;');
     var genEventHandlerSet = G.tuple(G.value(THIS), G.tuple(genJS));
     var largestSmallInteger = 63;
@@ -1299,6 +1302,12 @@
     argsByIdentity(HTMLInputElement.prototype.select, genNoArgMethod);
     argsByIdentity(HTMLInputElement.prototype.stepDown, genNoArgMethod);
     argsByIdentity(HTMLInputElement.prototype.stepUp, genNoArgMethod);
+    argsByIdentity(HTMLMediaElement.prototype.canPlayType,
+        genMethod(genMediaType));
+ argsByIdentity(HTMLMediaElement.prototype.fastSeek, genMethod(genNumber));
+    argsByIdentity(HTMLMediaElement.prototype.load, genNoArgMethod);
+    argsByIdentity(HTMLMediaElement.prototype.pause, genNoArgMethod);
+    argsByIdentity(HTMLMediaElement.prototype.play, genNoArgMethod);
     argsByIdentity(HTMLTableRowElement.prototype.insertCell,
         genMethod(genSmallInteger));
     argsByIdentity(HTMLTableRowElement.prototype.deleteCell,
@@ -1319,6 +1328,7 @@
         genMethod(genSmallInteger));
     argsByIdentity(Image, genNew());  // TODO args
     argsByIdentity(Option, genNew());  // TODO args
+    argsByIdentity(Audio, genNew());  // TODO args
     argsByIdentity(XMLHttpRequest, genNew());
argsByIdentity(XMLHttpRequest.prototype.open, G.none); // TODO stateful
     argsByIdentity(XMLHttpRequest.prototype.setRequestHeader, G.none);

--

--- You received this message because you are subscribed to the Google Groups "Google Caja Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to