Permalink
Browse files

Refactoring component loading strategies, including injecting base URI.

The "nodes" and "javascript" strategies are now transformed into a "html"
strategy behind the scenes. In practice, there's no reason to ever use the
"javascript" strategy, so it's now deprecated.

The "url" strategy is the most natural strategy, and no baseURI modifications
are necessary. However, it's slower than the other strategies, and more
difficult to do content protection (if you need that).

The other two strategies, "html" and "doc", are applied to an iframe that has
been primed with "about:blank". This means it has the baseURI of its parent
document. If there are any relative hrefs to resources in the component
(scripts, stylesheets, images, etc), and the component should have a different
baseURI, these resources will ordinarily not load.

To date, Monocle has said "this is the bookData object's problem". No longer.
Now Monocle automatically injects a <base> tag into components loaded with the
"doc" or "html" strategy. You can override this by inserting a <base> tag
manually, or by listening on the reader for a monocle:component:baseuri event
and calling preventDefault() on it.
  • Loading branch information...
1 parent 7418ec9 commit ce9be1e6711ed1f6bd97498c5ce990709c3add4c @joseph committed Sep 5, 2012
@@ -40,7 +40,7 @@ Monocle.bookDataFromIds = function (elementIds) {
return Monocle.bookData({
components: elementIds,
getComponent: function (cmptId) {
- return { nodes: [document.getElementById(cmptId).cloneNode(true)] }
+ return { nodes: [document.getElementById(cmptId)] }
}
});
}
View
@@ -42,6 +42,8 @@ Monocle.Component = function (book, id, index, chapters, source) {
// will be invoked with the pageDiv and this component as arguments.
//
function applyTo(pageDiv, callback) {
+ prepareSource(pageDiv.m.reader);
+
var evtData = { 'page': pageDiv, 'source': p.source };
pageDiv.m.reader.dispatchEvent('monocle:componentchanging', evtData);
@@ -74,20 +76,11 @@ Monocle.Component = function (book, id, index, chapters, source) {
// Hide the frame while we're changing it.
frame.style.visibility = "hidden";
- // Prevent about:blank overriding imported nodes in Firefox.
- // Disabled again because it seems to result in blank pages in Saf.
- //frame.contentWindow.stop();
-
- if (p.source.html || (typeof p.source == "string")) { // HTML
+ if (p.source.html) {
return loadFrameFromHTML(p.source.html || p.source, frame, callback);
- } else if (p.source.javascript) { // JAVASCRIPT
- //console.log("Loading as javascript: "+p.source.javascript);
- return loadFrameFromJavaScript(p.source.javascript, frame, callback);
- } else if (p.source.url) { // URL
+ } else if (p.source.url) {
return loadFrameFromURL(p.source.url, frame, callback);
- } else if (p.source.nodes) { // NODES
- return loadFrameFromNodes(p.source.nodes, frame, callback);
- } else if (p.source.doc) { // DOCUMENT
+ } else if (p.source.doc) {
return loadFrameFromDocument(p.source.doc, frame, callback);
}
}
@@ -119,29 +112,13 @@ Monocle.Component = function (book, id, index, chapters, source) {
}
- // LOAD STRATEGY: JAVASCRIPT
- // Like the HTML strategy, but assumes that the src string is already clean.
- //
- // DEPRECATED: HTML strategy is faster now, so this just unescapes the string
- // and loads it as HTML.
- //
- function loadFrameFromJavaScript(src, frame, callback) {
- console.deprecation("Use { 'html': src } -- no need to clean the string.");
- loadFrameFromHTML(eval("'"+src+"'"), frame, callback);
- }
-
-
// LOAD STRATEGY: URL
// Loads the URL into the given frame, invokes callback once loaded.
//
function loadFrameFromURL(url, frame, callback) {
- // If it's a relative path, we need to make it absolute, using the
- // reader's location (not the active component's location).
+ // If it's a relative path, we need to make it absolute.
if (!url.match(/^\//)) {
- var link = document.createElement('a');
- link.setAttribute('href', url);
- url = link.href;
- delete(link);
+ url = absoluteURL(url);
}
var fn = function () {
Monocle.Events.deafen(frame, 'load', fn);
@@ -152,58 +129,40 @@ Monocle.Component = function (book, id, index, chapters, source) {
}
- // LOAD STRATEGY: NODES
- // Loads the array of DOM nodes into the body of the frame (replacing all
- // existing nodes), then invokes the callback.
- //
- // DEPRECATED: now just transforms to HTML and loads as that.
- //
- function loadFrameFromNodes(nodes, frame, callback) {
- console.deprecation("Use { 'html': node.outerHTML } or similar.");
- var src = "";
- for (var i = 0; i < nodes.length; ++i) {
- src += nodes[i].outerHTML || '';
- }
- loadFrameFromHTML(src, frame, callback);
- }
-
-
// LOAD STRATEGY: DOCUMENT
// Replaces the DocumentElement of the given frame with the given srcDoc.
// Invokes the callback when loaded.
//
function loadFrameFromDocument(srcDoc, frame, callback) {
- var destDoc = frame.contentDocument;
-
- var srcBases = srcDoc.getElementsByTagName('base');
- if (srcBases[0]) {
- var head = destDoc.getElementsByTagName('head')[0];
- if (!head) {
- try {
- head = destDoc.createElement('head');
- if (destDoc.body) {
- destDoc.insertBefore(head, destDoc.body);
- } else {
- destDoc.appendChild(head);
+ var doc = frame.contentDocument;
+
+ // WebKit has an interesting quirk. The <base> tag must exist in the
+ // document being replaced, not the new document.
+ if (Monocle.Browser.is.WebKit) {
+ var srcBase = srcDoc.querySelector('base');
+ if (srcBase) {
+ var head = doc.querySelector('head');
+ if (!head) {
+ try {
+ head = doc.createElement('head');
+ prependChild(doc.documentElement, head);
+ } catch (e) {
+ head = doc.body;
}
- } catch (e) {
- head = destDoc.body;
}
+ var base = doc.createElement('base');
+ base.setAttribute('href', srcBase.href);
+ head.appendChild(base);
}
- var bases = destDoc.getElementsByTagName('base');
- var base = bases[0] ? bases[0] : destDoc.createElement('base');
- base.setAttribute('href', srcBases[0].getAttribute('href'));
- head.appendChild(base);
}
- destDoc.replaceChild(
- destDoc.importNode(srcDoc.documentElement, true),
- destDoc.documentElement
+ doc.replaceChild(
+ doc.importNode(srcDoc.documentElement, true),
+ doc.documentElement
);
- // DISABLED: immediate readiness - webkit has some difficulty with this.
- // if (callback) { callback(); }
-
+ // NB: It's a significant problem with this load strategy that there's
+ // no indication when it is complete.
Monocle.defer(callback);
}
@@ -349,6 +308,84 @@ Monocle.Component = function (book, id, index, chapters, source) {
}
+ function prepareSource(reader) {
+ if (p.sourcePrepared) { return; }
+ p.sourcePrepared = true;
+
+ if (typeof p.source == "string") {
+ p.source = { html: p.source };
+ }
+
+ // If supplied as escaped javascript, unescape it to HTML by evalling it.
+ if (p.source.javascript) {
+ console.deprecation(
+ "Loading a component by 'javascript' is deprecated. " +
+ "Use { 'html': src } -- no need to escape or clean the string."
+ );
+ p.source = { html: eval("'"+p.source.javascript+"'") };
+ }
+
+ // If supplied as DOM nodes, convert to HTML by concatenating outerHTMLs.
+ if (p.source.nodes) {
+ var srcs = [];
+ for (var i = 0, ii = p.source.nodes.length; i < ii; ++i) {
+ var node = p.source.nodes[i];
+ if (node.outerHTML) {
+ srcs.push(node.outerHTML);
+ } else {
+ var div = document.createElement('div');
+ div.appendChild(node.cloneNode(true));
+ srcs.push(div.innerHTML);
+ delete(div);
+ }
+ }
+ p.source = { html: srcs.join('') };
+ }
+
+ if (p.source.html && !p.source.html.match(new RegExp("<base\s.+>", "im"))) {
+ var baseURI = computeBaseURI(reader);
+ if (baseURI) {
+ p.source.html = p.source.html.replace(
+ new RegExp("(<head(\s[^>]*>)|>)", "im"),
+ '$1<base href="'+baseURI+'" />'
+ );
+ }
+ }
+
+ if (p.source.doc && !p.source.doc.querySelector('base')) {
+ var srcHead = p.source.doc.querySelector('head') || p.source.doc.body;
+ var baseURI = computeBaseURI(reader);
+ if (srcHead && baseURI) {
+ var srcBase = p.source.doc.createElement('base');
+ srcBase.setAttribute('href', baseURI);
+ prependChild(srcHead, srcBase);
+ }
+ }
+ }
+
+
+ function computeBaseURI(reader) {
+ var evtData = { cmptId: p.id, cmptURI: absoluteURL(p.id) }
+ if (reader.dispatchEvent('monocle:component:baseuri', evtData, true)) {
+ return evtData.cmptURI;
+ }
+ }
+
+
+ function absoluteURL(url) {
+ var link = document.createElement('a');
+ link.setAttribute('href', url);
+ result = link.href;
+ delete(link);
+ return result;
+ }
+
+
+ function prependChild(pr, el) {
+ pr.firstChild ? pr.insertBefore(el, pr.firstChild) : pr.appendChild(el);
+ }
+
+
API.applyTo = applyTo;
API.updateDimensions = updateDimensions;
API.chapterForPage = chapterForPage;
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>DOC-1</title>
+ </head>
+ <body>
+ <img src="../resources/monocle.png" />
+ <p>
+ A component loaded as a document will have its baseURI injected using the
+ componentId.
+ </p>
+ </body>
+</html>
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <!-- <script> -->
+ <!-- var rel = '/test/baseuris/cmpts/foobar/doc-2.html'; -->
+ <!-- document.write('<base href="'+location.origin+rel+'" />'); -->
+ <!-- </script> -->
+ <base href="http://monocle.dev/test/baseuris/cmpts/foobar/doc-2.html" />
+ <title>DOC-2</title>
+ </head>
+ <body>
+ <img src="../../resources/monocle.png" />
+ <p>
+ If you want to use your own baseURI, you can override it by supplying
+ a &lt;base&gt; tag in the header.
+ </p>
+ </body>
+</html>
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>DOC-3</title>
+ </head>
+ <body>
+ <img src="resources/monocle.png" />
+ <p>
+ If you don't need baseURI injection (because all resources are referenced
+ relative to the parent document's baseURI), you can listen for and cancel
+ the <code>monocle:component:baseuri</code> event.
+ </p>
+ </body>
+</html>
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>DOC-4</title>
+ </head>
+ <body>
+ <img src="../resources/monocle.png" />
+ <p>
+ If the component id is NOT a relative path from the parent document's
+ baseURI, you should listen for the <code>monocle:component:baseuri</code>
+ event and change the value of <code>evt.m.cmptURI</code>.
+ </p>
+ </body>
+</html>
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>DOC-5</title>
+ </head>
+ <body>
+ <img src="resources/monocle.png" />
+ <p>
+ If the component id is just a string without forward slashes, and
+ resources are relative to the parent document's baseURI, you're all good.
+ </p>
+ </body>
+</html>
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>URL-1</title>
+ </head>
+ <body>
+ <img src="../resources/monocle.png" />
+ <p>A component loaded by URL has a built-in baseURI.</p>
+ </body>
+</html>
Oops, something went wrong.

0 comments on commit ce9be1e

Please sign in to comment.