From b45e6a7cc9470de202bf0490ebd36c9a8419dbbc Mon Sep 17 00:00:00 2001 From: Rob Wu Date: Sat, 23 Apr 2016 23:52:22 +0200 Subject: [PATCH] Don't use shadow DOM for rendering / Multiple shadow roots are not supported any more in Chrome 51+ (https://crbug.com/603448#c6), so this patch changes the way that PDFs are rendered in `` / `` tags. I used shadow roots because their content is not visible from the web page, so the odds of conflicts were minimal. Now I have to render the PDF frame directly in the page, which can be observed from the page (unfortunately). Now the following happens when an embedded PDF tag is detected: - `` tags: The type and src attributes are updated. - `` tags: The type attribute is changed and the fallback content is set and displayed. --- extensions/chromium/contentscript.js | 159 ++++++++++++++++++--------- 1 file changed, 107 insertions(+), 52 deletions(-) diff --git a/extensions/chromium/contentscript.js b/extensions/chromium/contentscript.js index c78cf4ad3..c2e899114 100644 --- a/extensions/chromium/contentscript.js +++ b/extensions/chromium/contentscript.js @@ -23,31 +23,10 @@ function getViewerURL(pdf_url) { return VIEWER_URL + '?file=' + encodeURIComponent(pdf_url); } -// (un)prefixed property names -var createShadowRoot, shadowRoot; -if (typeof Element.prototype.createShadowRoot !== 'undefined') { - // Chrome 35+ - createShadowRoot = 'createShadowRoot'; - shadowRoot = 'shadowRoot'; -} else if (typeof Element.prototype.webkitCreateShadowRoot !== 'undefined') { - // Chrome 25 - 34 - createShadowRoot = 'webkitCreateShadowRoot'; - shadowRoot = 'webkitShadowRoot'; - try { - document.createElement('embed').webkitCreateShadowRoot(); - } catch (e) { - // Only supported since Chrome 33. - createShadowRoot = shadowRoot = ''; - } -} - -// Only observe the document if we can make use of Shadow DOM. -if (createShadowRoot) { - if (CSS.supports('animation', '0s')) { - document.addEventListener('animationstart', onAnimationStart, true); - } else { - document.addEventListener('webkitAnimationStart', onAnimationStart, true); - } +if (CSS.supports('animation', '0s')) { + document.addEventListener('animationstart', onAnimationStart, true); +} else { + document.addEventListener('webkitAnimationStart', onAnimationStart, true); } function onAnimationStart(event) { @@ -57,10 +36,9 @@ function onAnimationStart(event) { } // Called for every or element in the page. -// It does not trigger any Mutation observers, but it may modify the -// shadow DOM rooted under the given element. -// Calling this function multiple times for the same element is safe, i.e. -// it has no side effects. +// This may change the type, src/data attributes and/or the child nodes of the +// element. This function only affects elements for the first call. Subsequent +// invocations have no effect. function watchObjectOrEmbed(elem) { var mimeType = elem.type; if (mimeType && 'application/pdf' !== mimeType.toLowerCase()) { @@ -86,33 +64,35 @@ function watchObjectOrEmbed(elem) { return; } - if (elem[shadowRoot]) { - // If the element already has a shadow root, assume that we've already - // seen this element. + if (elem.__I_saw_this_element) { return; } - elem[createShadowRoot](); + elem.__I_saw_this_element = true; + + var tagName = elem.tagName.toUpperCase(); + var updateEmbedOrObject; + if (tagName === 'EMBED') { + updateEmbedOrObject = updateEmbedElement; + } else if (tagName === 'OBJECT') { + updateEmbedOrObject = updateObjectElement; + } else { + return; + } + + var lastSrc; + var isUpdating = false; function updateViewerFrame() { - var path = elem[srcAttribute]; - if (!path) { - elem[shadowRoot].textContent = ''; - } else { - elem[shadowRoot].innerHTML = - // Set display: inline-block; to the host element (/) to - // ensure that the dimensions defined on the host element are applied to - // the iframe (http://crbug.com/358648). - // The styles are declared in the shadow DOM to allow page authors to - // override these styles (e.g. .style.display = 'none';). - '' + - ''; - elem[shadowRoot].lastChild.src = getEmbeddedViewerURL(path); + if (!isUpdating) { + isUpdating = true; + try { + if (lastSrc !== elem[srcAttribute]) { + updateEmbedOrObject(elem); + lastSrc = elem[srcAttribute]; + } + } finally { + isUpdating = false; + } } } @@ -128,6 +108,81 @@ function watchObjectOrEmbed(elem) { }); } +// Display the PDF Viewer in an . +function updateEmbedElement(elem) { + if (elem.type === 'text/html' && elem.src.lastIndexOf(VIEWER_URL, 0) === 0) { + // The viewer is already shown. + return; + } + // The tag needs to be removed and re-inserted before any src changes + // are effective. + var parentNode = elem.parentNode; + var nextSibling = elem.nextSibling; + if (parentNode) { + parentNode.removeChild(elem); + } + elem.type = 'text/html'; + elem.src = getEmbeddedViewerURL(elem.src); + if (parentNode) { + parentNode.insertBefore(elem, nextSibling); + } +} + +// Display the PDF Viewer in an . +function updateObjectElement(elem) { + // elements are terrible. Experiments (in49.0.2623.75) show that the + // following happens: + // - When fallback content is shown (e.g. because the built-in PDF Viewer is + // disabled), updating the "data" attribute has no effect. Not surprising + // considering that HTMLObjectElement::m_useFallbackContent is not reset + // once it is set to true. Source: + // WebKit/Source/core/html/HTMLObjectElement.cpp#378 (rev 749fe30d676b6c14). + // - When the built-in PDF Viewer plugin is enabled, updating the "data" + // attribute reloads the content (provided that the type was correctly set). + // - When is used + // (tested with a data-URL, data:text/html,, the extension's + // origin whitelist is not set up, so the viewer can't load the PDF file. + // - The content of the tag may be affected by tags. + // + // To make sure that our solution works for all cases, we will insert a frame + // as fallback content and force the tag to render its fallback + // content. + var iframe = elem.firstElementChild; + if (!iframe || !iframe.__inserted_by_pdfjs) { + iframe = createFullSizeIframe(); + elem.textContent = ''; + elem.appendChild(iframe); + iframe.__inserted_by_pdfjs = true; + } + iframe.src = getEmbeddedViewerURL(elem.data); + + // Some bogus content type that is not handled by any plugin. + elem.type = 'application/not-a-pee-dee-eff-type'; + // Force the to reload and render its fallback content. + elem.data += ''; +} + +// Create an