/*
Copyright (c) 2012, Motorola Mobility LLC.
All Rights Reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Motorola Mobility LLC nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
////////////////////////////////////////////////////////////////////////
//
var Montage = require("montage/core/core").Montage,
BaseDocumentView = require("js/document/views/base").BaseDocumentView,
ElementModel = require("js/models/element-model");
////////////////////////////////////////////////////////////////////////
//
exports.DesignDocumentView = Montage.create(BaseDocumentView, {
////////////////////////////////////////////////////////////////////
//
hasTemplate: {
value: false
},
////////////////////////////////////////////////////////////////////
//
_callback: {
value: null
},
////////////////////////////////////////////////////////////////////
//
_viewCallback: {
value: null
},
////////////////////////////////////////////////////////////////////
//
_template: {
value: null
},
////////////////////////////////////////////////////////////////////
//
_bodyFragment: {
value: null
},
////////////////////////////////////////////////////////////////////
//
_headFragment: {
value: null
},
////////////////////////////////////////////////////////////////////
//
_observer: {
value: {head: null, body: null}
},
////////////////////////////////////////////////////////////////////
//
content: {
value: null
},
////////////////////////////////////////////////////////////////////
//
_liveNodeList: {
value: null
},
////////////////////////////////////////////////////////////////////
//
_webGlHelper: {
value: null
},
////////////////////////////////////////////////////////////////////
//
_baseHref: {
value: null
},
////////////////////////////////////////////////////////////////////
//
baseHref: {
get: function() {return this._baseHref;},
set: function(value) {this._baseHref = value;}
},
////////////////////////////////////////////////////////////////////
//
_document: {
value: null
},
////////////////////////////////////////////////////////////////////
//
document: {
get: function() {return this._document;},
set: function(value) {this._document = value;}
},
////////////////////////////////////////////////////////////////////
//
_documentRoot: {
value: null
},
////////////////////////////////////////////////////////////////////
//
documentRoot: {
get: function() {return this._documentRoot;},
set: function(value) {this._documentRoot = value;}
},
////////////////////////////////////////////////////////////////////
//
getLiveNodeList: {
value: function(useFilter) {
if(useFilter) {
var filteredNodes = [],
childNodes = Array.prototype.slice.call(this._liveNodeList, 0);
childNodes.forEach(function(item) {
if( (item.nodeType === 1) && (item.nodeName !== "STYLE") && (item.nodeName !== "SCRIPT")) {
filteredNodes.push(item);
}
});
return filteredNodes;
} else {
return Array.prototype.slice.call(this._liveNodeList, 0);
}
}
},
////////////////////////////////////////////////////////////////////
//
initialize: {
value: function (parent) {
//Creating iFrame for view
this.iframe = document.createElement("iframe");
//Setting default styles
this.iframe.style.border = "none";
this.iframe.style.background = "#FFF";
this.iframe.style.height = "100%";
this.iframe.style.width = "100%";
//Returning reference to iFrame created
return parent.appendChild(this.iframe);
}
},
////////////////////////////////////////////////////////////////////
//
render: {
value: function (callback, template, viewCallback) {
//TODO: Remove, this is a temp patch for webRequest API gate
this.application.ninja.documentController.redirectRequests = false;
//Storing callback for dispatch ready
this._callback = callback;
this._template = template;
this._viewCallback = viewCallback;
//Adding listener to know when template is loaded to then load user content
this.iframe.addEventListener("load", this.onTemplateLoad.bind(this), false);
//TODO: Add source parameter and root (optional)
if (template && template.type === 'banner' && template.size) {
this.iframe.src = "js/document/templates/banner/index.html";
} else {
this.iframe.src = "js/document/templates/html/index.html";
}
}
},
////////////////////////////////////////////////////////////////////
//
onTemplateLoad: {
value: function (e) {
//TODO: Remove, this is a temp patch for webRequest API gate
this.application.ninja.documentController.redirectRequests = true;
//TODO: Add support to constructing URL with a base HREF
var basetag = this.content.document.getElementsByTagName('base');
//Removing event
this.iframe.removeEventListener("load", this.onTemplateLoad.bind(this), false);
//TODO: Improve usage of this reference
this.document = this.iframe.contentWindow.document;
//Looping through template styles and marking them with ninja data attribute for I/O clean up
for (var k in this.document.styleSheets) {
if (this.document.styleSheets[k].ownerNode && this.document.styleSheets[k].ownerNode.setAttribute) {
this.document.styleSheets[k].ownerNode.setAttribute('data-ninja-template', 'true');
}
}
//Checking for a base URL
if (basetag.length) {
if (basetag[basetag.length-1].getAttribute && basetag[basetag.length-1].getAttribute('href')) {
//Setting base HREF in model
this.baseHref = basetag[basetag.length-1].getAttribute('href');
}
}
//Checking to content to be template
if (this._template) {
if (this._template.type === 'banner') {
//Loading contents into a fragment
this._bodyFragment = this.document.createElement('body');
//Listening for content to be ready
this._observer.body = new WebKitMutationObserver(this.insertBannerContent.bind(this));
this._observer.body.observe(this._bodyFragment, {childList: true});
//Inserting
HTML and parsing URLs via mediator method
this._bodyFragment.innerHTML = ''+(this.content.body.replace(/\b(href|src)\s*=\s*"([^"]*)"/g, this.application.ninja.ioMediator.tmplt.getNinjaPropUrlRedirect.bind(this.application.ninja.ioMediator.tmplt))).replace(/url\(([^"]*)(.+?)\1\)/g, this.application.ninja.ioMediator.tmplt.getNinjaPropUrlRedirect.bind(this.application.ninja.ioMediator.tmplt));
}
} else {
//Creating temp code fragement to load head
this._headFragment = this.document.createElement('head');
//Adding event listener to know when head is ready, event only dispatched once when using innerHTML
this._observer.head = new WebKitMutationObserver(this.insertHeadContent.bind(this));
this._observer.head.observe(this._headFragment, {childList: true});
//Inserting HTML and parsing URLs via mediator method
this._headFragment.innerHTML = (this.content.head.replace(/\b(href|src)\s*=\s*"([^"]*)"/g, this.application.ninja.ioMediator.tmplt.getNinjaPropUrlRedirect.bind(this.application.ninja.ioMediator.tmplt))).replace(/url\(([^"]*)(.+?)\1\)/g, this.application.ninja.ioMediator.tmplt.getNinjaPropUrlRedirect.bind(this.application.ninja.ioMediator.tmplt));
//Adding event listener to know when the body is ready and make callback (using HTML5 new DOM Mutation Events)
this._observer.body = new WebKitMutationObserver(this.bodyContentLoaded.bind(this));
this._observer.body.observe(this.document.body, {childList: true});
//Inserting HTML and parsing URLs via mediator method
this.document.body.innerHTML += ''+(this.content.body.replace(/\b(href|src)\s*=\s*"([^"]*)"/g, this.application.ninja.ioMediator.tmplt.getNinjaPropUrlRedirect.bind(this.application.ninja.ioMediator.tmplt))).replace(/url\(([^"]*)(.+?)\1\)/g, this.application.ninja.ioMediator.tmplt.getNinjaPropUrlRedirect.bind(this.application.ninja.ioMediator.tmplt));
//Copying attributes to maintain same properties as the
for (var n in this.content.document.body.attributes) {
if (this.content.document.body.attributes[n].value) {
this.document.body.setAttribute(this.content.document.body.attributes[n].name, this.content.document.body.attributes[n].value);
}
}
//Copying attributes to maintain same properties as the
for (var m in this.content.document.head.attributes) {
if (this.content.document.head.attributes[m].value) {
this.document.head.setAttribute(this.content.document.head.attributes[m].name, this.content.document.head.attributes[m].value);
}
}
//Copying attributes to maintain same properties as the
var htmlTagMem = this.content.document.getElementsByTagName('html')[0], htmlTagDoc = this.document.getElementsByTagName('html')[0];
for (var m in htmlTagMem.attributes) {
if (htmlTagMem.attributes[m].value) {
htmlTagDoc.setAttribute(htmlTagMem.attributes[m].name, htmlTagMem.attributes[m].value);
}
}
}
}
},
////////////////////////////////////////////////////////////////////
//
insertBannerContent: {
value: function (e) {
//Getting first element in DOM (assumes it's root)
//TODO: Ensure wrapper logic is proper
var wrapper = this._bodyFragment.getElementsByTagName('*')[1],
banner = this._bodyFragment.getElementsByTagName('*')[2],
ninjaBanner = this.document.body.getElementsByTagName('ninja-content')[0],
ninjaWrapper = this.document.body.getElementsByTagName('ninja-viewport')[0];
//Copying attributes to maintain same properties as the banner wrapper
for (var n in wrapper.attributes) {
if (wrapper.attributes[n].value) {
ninjaWrapper.setAttribute(wrapper.attributes[n].name, wrapper.attributes[n].value);
}
}
//Copying attributes to maintain same properties as the banner content
for (var n in banner.attributes) {
if (banner.attributes[n].value) {
ninjaBanner.setAttribute(banner.attributes[n].name, banner.attributes[n].value);
}
}
//Adjusting margin per size of document
this.document.head.getElementsByTagName('style')[0].innerHTML += '\n ninja-viewport {overflow: visible !important;} ninja-content, ninja-viewport {width: ' + this._template.size.width + 'px; height: ' + this._template.size.height + 'px;}';
//Setting content in template
ninjaBanner.innerHTML = banner.innerHTML;
//Garbage collection
this._bodyFragment = null;
//Calling standard method to finish opening document
this.bodyContentLoaded(null);
}
},
////////////////////////////////////////////////////////////////////
//
insertHeadContent: {
value: function (e) {
//Removing event
this._observer.head.disconnect();
this._observer.head = null;
//Adding the loaded nodes from code fragment into actual document head
for(var i in this._headFragment.childNodes) {
//Minor hack to know node is actual HTML node
if(this._headFragment.childNodes[i].outerHTML) {
this.document.head.appendChild(this._headFragment.childNodes[i]);
}
}
//Garbage collection
this._headFragment = null;
}
},
////////////////////////////////////////////////////////////////////
//
bodyContentLoaded: {
value: function (e) {
//Removing event, only needed on initial load
this._observer.body.disconnect();
this._observer.body = null;
//Removing loading container (should be removed)
this.document.body.removeChild(this.document.getElementsByTagName('ninjaloadinghack')[0]);
//Getting style and link tags in document
var htags = this.document.getElementsByTagName('html'),
bannerWrapper,
userStyles,
stags = this.document.getElementsByTagName('style'),
ltags = this.document.getElementsByTagName('link'), i, orgNodes,
scripttags = this.document.getElementsByTagName('script'),
videotags = this.document.getElementsByTagName('video');
//Temporarily checking for disabled special case (we must enabled for Ninja to access styles)
this.ninjaDisableAttribute(stags);
this.ninjaDisableAttribute(ltags);
//Looping through all link tags to reload into style tags
if(ltags.length > 0) {
for (i = 0; i < ltags.length; i++) {
//
if (ltags[i].href) {
//Inseting