/* 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