從“全局/窗口”導入窗口;
從“全局/文檔”導入文檔;
從 '../utils/merge-options' 導入 mergeOptions;
從 '../utils/url' 導入 {getAbsoluteURL};

/**
 * 此函數用於在有內容時觸發源集
 * 類似於被調用的 `mediaEl.load()`。它將嘗試通過以下方式找到來源
 * `src` 屬性,然後是 `<source>` 元素。然後它會觸發 `sourceset`
 * 如果我們不知道,則使用找到的源或空字符串。如果不能
 * 找到源然後 `sourceset` 將不會被解僱。
 *
 * @param {Html5} 技術
 * 設置 sourceset 的技術對象
 *
 * @return {布爾值}
 * 如果源集未被觸發則返回 false,否則返回 true。
 */
const sourcesetLoad = (tech) => {
  const el = tech.el();

  // 如果設置了 `el.src`,將加載該源。
  如果(el.hasAttribute('src')){
    tech.triggerSourceset(el.src);
    返回真;
  }

  /**
   * 由於媒體元素上沒有 src 屬性,源元素將用於
   * 實施源選擇算法。這是異步發生的,並且
   * 在大多數情況下,如果有多個來源,我們無法確定哪個來源會
   * 加載,無需重新實現源選擇算法。這個時候我們不
   * 打算這樣做。不過,我們確實在這里處理了三種特殊情況:
   *
   * 1。如果沒有來源,請不要觸發 `sourceset`。
   * 2。如果只有一個帶有 `src` 屬性的 `<source>` 就是我們的 `src`
   * 3。如果有多個 `<source>` 但它們都具有相同的 `src` url。
   * 那將是我們的 src。
   */
  const sources = tech.$$('來源');
  常量 srcUrls = [];
  讓 src = '';

  // 如果沒有源,不觸發源集
  如果(!sources.length){
    返回假;
  }

  // 只計算有效/不重複的源元素
  為 (讓我 = 0; 我 < 來源. 長度; 我 ++) {
    const url = 來源[i].src;

    如果 (url && srcUrls.indexOf(url) === -1) {
      srcUrls.push(url);
    }
  }

  // 沒有有效的來源
  如果(!srcUrls.length){
    返回假;
  }

  // 只有一個有效的源元素 url
  // 使用那個
  如果(srcUrls.length === 1){
    src = srcUrls[0];
  }

  tech.triggerSourceset(src);
  返回真;
};

/**
 * 我們為瀏覽器實現的 `innerHTML` 描述符
 * 沒有。
 */
const innerHTMLDescriptorPolyfill = Object.defineProperty({}, 'innerHTML', {
  得到() {
    返回 this.cloneNode(true).innerHTML;
  },
  設置(v){
    // 創建一個虛擬節點以在其上使用 innerHTML
    const dummy = document.createElement(this.nodeName.toLowerCase());

    // 將 innerHTML 設置為提供的值
    dummy.innerHTML = v;

    // 製作一個文檔片段來保存來自 dummy 的節點
    const docFrag = document.createDocumentFragment();

    // 將 innerHTML 創建的所有節點複製到 dummy
    // 到文檔片段
    while (dummy.childNodes.length) {
      docFrag.appendChild(dummy.childNodes[0]);
    }

    // 刪除內容
    this.innerText = '';

    // 現在我們通過附加
    // 文檔片段。這就是如何做到這一點。
    window.Element.prototype.appendChild.call(this, docFrag);

    // 然後返回 innerHTML 的 setter 的結果
    返回 this.innerHTML;
  }
});

/**
 * 獲取屬性描述符給定優先級列表和
 * 財產得到。
 */
const getDescriptor = (priority, prop) => {
  讓描述符 = {};

  對於(讓我 = 0; 我 < 優先級。長度; 我 ++){
    descriptor = Object.getOwnPropertyDescriptor(priority[i], prop);

    如果(描述符 && descriptor.set && descriptor.get){
      休息;
    }
  }

  descriptor.enumerable = true;
  descriptor.configurable = true;

  返回描述符;
};

const getInnerHTMLDescriptor = (tech) => getDescriptor([
  tech.el(),
  window.HTMLMediaElement.prototype,
  window.Element.prototype,
  innerHTMLDescriptorPolyfill
], 'innerHTML');

/**
 * 修補瀏覽器內部函數,以便我們可以同步判斷
 * 如果將 `<source>` 附加到媒體元素。出於某種原因,這
 * 如果媒體元素已準備就緒且沒有源,則會導致 `sourceset`。
 * 這發生在:
 * - 頁面剛剛加載,媒體元素沒有來源。
 * - 媒體元素清空了所有來源,然後調用了 load() 。
 *
 * 它通過在支持以下功能/屬性時修補它們來實現:
 *
 * - `append()` - 可用於將 `<source>` 元素添加到媒體元素
 * - `appendChild()` - 可用於將 `<source>` 元素添加到媒體元素
 * - `insertAdjacentHTML()` - 可用於將 `<source>` 元素添加到媒體元素
 * - `innerHTML` - 可用於將 `<source>` 元素添加到媒體元素
 *
 * @param {Html5} 技術
 * 正在設置 sourceset 的技術對象。
 */
const firstSourceWatch = 函數(技術){
  const el = tech.el();

  // 確保 firstSourceWatch 沒有設置兩次。
  如果(el.resetSourceWatch_){
    返回;
  }

  常量舊 = {};
  const innerDescriptor = getInnerHTMLDescriptor(技術);
  const appendWrapper = (appendFn) => (...args) => {
    const retval = appendFn.apply(el, args);

    sourcesetLoad(技術);

    返回retval;
  };

  ['append', 'appendChild', 'insertAdjacentHTML'].forEach((k) => {
    如果(!埃爾 [k]) {
      返回;
    }

    // 存儲舊函數
    舊[k] = el[k];

    // 如果有源,則使用源集調用舊函數
    // 已加載
    el[k] = appendWrapper(舊[k]);
  });

  Object.defineProperty(el, 'innerHTML', mergeOptions(innerDescriptor, {
    設置:appendWrapper(innerDescriptor.set)
  }));

  el.resetSourceWatch_ = () => {
    el.resetSourceWatch_ = null;
    Object.keys(old).forEach((k) => {
      el[k] = 舊[k];
    });

    Object.defineProperty(el, 'innerHTML', innerDescriptor);
  };

  // 在第一個源集上,我們需要還原我們的更改
  tech.one('sourceset', el.resetSourceWatch_);
};

/**
 * 我們為瀏覽器實現的 `src` 描述符
 * 沒有。
 */
const srcDescriptorPolyfill = Object.defineProperty({}, 'src', {
  得到() {
    如果 (this.hasAttribute('src')) {
      返回 getAbsoluteURL(window.Element.prototype.getAttribute.call(this, 'src'));
    }

    返回 '';
  },
  設置(v){
    window.Element.prototype.setAttribute.call(this, 'src', v);

    返回 v;
  }
});

const getSrcDescriptor = (tech) => getDescriptor([tech.el(), window.HTMLMediaElement.prototype, srcDescriptorPolyfill], 'src');

/**
 * 在 `Html5` 技術上設置 `sourceset` 處理。這個功能
 * 修補以下元素屬性/功能:
 *
 * - `src` - 確定何時設置 `src`
 * - `setAttribute()` - 確定何時設置 `src`
 * - `load()` - 這會重新觸發源選擇算法,並且可以
 * 導致源集。
 *
 * 如果在我們添加 `sourceset` 支持時或在 `load()` 期間沒有源
 * 我們還修補了 `firstSourceWatch` 中列出的函數。
 *
 * @param {Html5} 技術
 * 打補丁的技術
 */
const setupSourceset = 函數(技術){
  如果(!tech.featuresSourceset){
    返回;
  }

  const el = tech.el();

  // 確保 sourceset 沒有設置兩次。
  如果(el.resetSourceset_){
    返回;
  }

  const srcDescriptor = getSrcDescriptor(技術);
  const oldSetAttribute = el.setAttribute;
  const oldLoad = el.load;

  Object.defineProperty(el, 'src', mergeOptions(srcDescriptor, {
    設置:(v)=> {
      const retval = srcDescriptor.set.call(el, v);

      // 我們在這裡使用 getter 獲取 src 上設置的實際值
      tech.triggerSourceset(el.src);

      返回retval;
    }
  }));

  el.setAttribute = (n, v) => {
    const retval = oldSetAttribute.call(el, n, v);

    如果 ((/src/i).test(n)) {
      tech.triggerSourceset(el.src);
    }

    返回retval;
  };

  el.load = () => {
    const retval = oldLoad.call(el);

    // 如果調用了 load,但沒有觸發源
    // 源碼開啟。我們必須注意源追加
    // 因為當媒體元素出現時可以觸發 `sourceset`
    //沒有來源
    如果(!sourcesetLoad(技術)){
      tech.triggerSourceset('');
      firstSourceWatch(技術);
    }

    返回retval;
  };

  如果(el.currentSrc){
    tech.triggerSourceset(el.currentSrc);
  } 否則,如果(!來源載入 (技術)) {
    firstSourceWatch(技術);
  }

  el.resetSourceset_ = () => {
    el.resetSourceset_ = null;
    el.load = oldLoad;
    el.setAttribute = oldSetAttribute;
    Object.defineProperty(el, 'src', srcDescriptor);
    如果(el.resetSourceWatch_){
      el.resetSourceWatch_();
    }
  };
};

導出默認設置源集;