dseif / slide-drive

Slideshow using audio/video to drive content
45 stars 9 forks source link

Embedded images not always showing up #66

Open banksJeremy opened 12 years ago

banksJeremy commented 12 years ago

Embedded images often don't show up. I'm not sure why. They are embedded in the document (I can load the data: url and see the picture), and they seem to be in the slide at the correct location. If I hover over their element in the debug tools, it will highlight a region in the middle of the slide, but nothing will be visible there.

If I open the SVG directly in the browser the image will be visible, so this is probably my fault.

Figure this out.

banksJeremy commented 12 years ago

Observed difference between element in .svg and element in imported .html:

Manually changing these back didn't fix it.

When I view the element in the document inspector the href property is always collapsed in the .HTML, but displayed in-full in the .SVG. The actual value is the same, but it's possible that this hints at a difference.

banksJeremy commented 12 years ago

It seems link xlink:href might be the problem...

Using href, image not visible: http://jsfiddle.net/jeremy/qHEWG/
Using xlink:href, image visible: http://jsfiddle.net/jeremy/qHEWG/1/

banksJeremy commented 12 years ago

It looks like this is caused by converting the SVG it to source and then re-parsing it. Chrome strips the namespaces when I do this, and henceforth ignores the namespaces when I set new attributes.

Given that SVGs are explicitly mentioned in the HTML fragment serialization algorithm in HTML5, you would expect them to round-trip without doing something stupid like this.

This year-old Chromium bug report sounds similar, but his test case is passing for me, so I guess it's not the same.

Firefox does not have this problem.

banksJeremy commented 12 years ago

I made a nice little test case to demonstrate the problem and stumbled upon a stupid, hacky but working solution. Before re-parsing, do this to each image element:

  var href = imageEl.getAttribute( "xlink:href" );
  imageEl.removeAttribute( "xlink:href" );
  imageEl.setAttribute( "xlink:href", href );

...yup.

(Aside: I realized that I'm not supposed to expect modifying attributes in the parsed document to have an effect, it just happens to do so for some attributes. Chrome not reflecting these changes was not a bug.)

banksJeremy commented 12 years ago

I realized that this issue also occurs a second time when we're exporting the finished document through Butter, so we need to "fix" the attributes again after they're reparsed.

I wanted to make a function that would find any elements with xlink:href attributes and fix them. Unfortunately, the spiffy new querySelector methods aren't namespace-friendly so I've been mucking around with Xpath.

Apparently Xpath usually won't return results for an unrooted tree, making this even more of a pain to deal with. Maybe I'll create an XML/SVG document in-memory and use that? I should experiment with this; maybe inserting into an SVG document first and then moving the nodes over to an HTML document will make things behave better...

edit: forget about trying to be general and perfect and actually understanding what's going on. I'll just use our existing code. For the record, here's what I was trying:

  function fixSvgHrefs( root ) {
    function nsResolver( prefix ) {
      return prefix == "xlink" ? "http://www.w3.org/1999/xlink"
           : prefix == "svg" ? "http://www.w3.org/2000/svg"
           : null;
    }

    var iter = document.evaluate( '//svg:svg//*[@href]', root || document, nsResolver, XPathResult.UNORDERED_NODE_ITERATOR_TYPE ),
        el;

    while( el = iter.iterateNext() ) {
      var href = el.getAttribute( "href" );
      el.removeAttribute( "href" );
      el.setAttribute( "xlink:href", href );
    }
  }
banksJeremy commented 12 years ago

I made a test page to evaluate this behaviour.

When parsing an xlink:href attribute of an <image> element in an embedded <svg>, Chrome and Firefox both create an attribute named xlink:href in the namespace http://www.w3.org/1999/xlink with the localName href.

When converting to HTML, Firefox uses the full name, xlink:href=. However, Chrome uses the localName href=. When parsed again, this produces an href attribute without a namespace, which works in neither browsers.

If you create an attribute with no namespace and a localName of xlink:href, Chrome will use it but Firefox will not.


Chrome requires an element with a full name of xlink:href to display correctly. Chrome requires an element with a localName of xlink:href to serialize correctly. Firefox requires an element with a localName of href in the xlink namespace to display and serialize correctly.

This explains why removing and re-adding the attribute using the namespace-insensitive methods fixed things in Chrome, but broke them in Firefox.

Since Chrome and Firefox require an attribute of the same name but with different properties there may be no general solution; feature/browser detection may be required.

Ugly, but not too much of a pain:

var tmp = document.createElement( "div" )
tmp.innerHTML = "<svg><image xlink:href=\"about:blank\"></image></svg>";
var dropsXlinkNamespace = !/xlink:href/.test( tmp.innerHTML );
banksJeremy commented 12 years ago

I'm still not entirely sure what's going on, but this is now working in Chrome and Firefox, including exported presentations and without breaking text selection.